1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135 |
- // Copyright (c) ZeniMax Media Inc.
- // Licensed under the GNU General Public License 2.0.
- #include "g_local.h"
- #include "g_statusbar.h"
- /*
- ======================================================================
- INTERMISSION
- ======================================================================
- */
- void DeathmatchScoreboard(edict_t *ent);
- void MoveClientToIntermission(edict_t *ent)
- {
- // [Paril-KEX]
- if (ent->client->ps.pmove.pm_type != PM_FREEZE)
- ent->s.event = EV_OTHER_TELEPORT;
- if (deathmatch->integer)
- ent->client->showscores = true;
- ent->s.origin = level.intermission_origin;
- ent->client->ps.pmove.origin = level.intermission_origin;
- ent->client->ps.viewangles = level.intermission_angle;
- ent->client->ps.pmove.pm_type = PM_FREEZE;
- ent->client->ps.gunindex = 0;
- ent->client->ps.gunskin = 0;
- ent->client->ps.damage_blend[3] = ent->client->ps.screen_blend[3] = 0;
- ent->client->ps.rdflags = RDF_NONE;
- // clean up powerup info
- ent->client->quad_time = 0_ms;
- ent->client->invincible_time = 0_ms;
- ent->client->breather_time = 0_ms;
- ent->client->enviro_time = 0_ms;
- ent->client->invisible_time = 0_ms;
- ent->client->grenade_blew_up = false;
- ent->client->grenade_time = 0_ms;
-
- ent->client->showhelp = false;
- ent->client->showscores = false;
- globals.server_flags &= ~SERVER_FLAG_SLOW_TIME;
- // RAFAEL
- ent->client->quadfire_time = 0_ms;
- // RAFAEL
- // ROGUE
- ent->client->ir_time = 0_ms;
- ent->client->nuke_time = 0_ms;
- ent->client->double_time = 0_ms;
- ent->client->tracker_pain_time = 0_ms;
- // ROGUE
- ent->viewheight = 0;
- ent->s.modelindex = 0;
- ent->s.modelindex2 = 0;
- ent->s.modelindex3 = 0;
- ent->s.modelindex = 0;
- ent->s.effects = EF_NONE;
- ent->s.sound = 0;
- ent->solid = SOLID_NOT;
- ent->movetype = MOVETYPE_NOCLIP;
- gi.linkentity(ent);
- // add the layout
- if (deathmatch->integer)
- {
- DeathmatchScoreboard(ent);
- ent->client->showscores = true;
- }
- }
- // [Paril-KEX] update the level entry for end-of-unit screen
- void G_UpdateLevelEntry()
- {
- if (!level.entry)
- return;
-
- level.entry->found_secrets = level.found_secrets;
- level.entry->total_secrets = level.total_secrets;
- level.entry->killed_monsters = level.killed_monsters;
- level.entry->total_monsters = level.total_monsters;
- }
- inline void G_EndOfUnitEntry(std::stringstream &layout, const int &y, const level_entry_t &entry)
- {
- layout << G_Fmt("yv {} ", y);
- // we didn't visit this level, so print it as an unknown entry
- if (!*entry.pretty_name)
- {
- layout << "table_row 1 ??? ";
- return;
- }
- layout << G_Fmt("table_row 4 \"{}\" ", entry.pretty_name) <<
- G_Fmt("{}/{} ", entry.killed_monsters, entry.total_monsters) <<
- G_Fmt("{}/{} ", entry.found_secrets, entry.total_secrets);
- int32_t minutes = entry.time.milliseconds() / 60000;
- int32_t seconds = (entry.time.milliseconds() / 1000) % 60;
- int32_t milliseconds = entry.time.milliseconds() % 1000;
- layout << G_Fmt("{:02}:{:02}:{:03} ", minutes, seconds, milliseconds);
- }
- void G_EndOfUnitMessage()
- {
- // [Paril-KEX] update game level entry
- G_UpdateLevelEntry();
- std::stringstream layout;
- // sort entries
- std::sort(game.level_entries.begin(), game.level_entries.end(), [](const level_entry_t &a, const level_entry_t &b) {
- int32_t a_order = a.visit_order ? a.visit_order : (*a.pretty_name ? (MAX_LEVELS_PER_UNIT + 1) : (MAX_LEVELS_PER_UNIT + 2));
- int32_t b_order = b.visit_order ? b.visit_order : (*b.pretty_name ? (MAX_LEVELS_PER_UNIT + 1) : (MAX_LEVELS_PER_UNIT + 2));
- return a_order < b_order;
- });
- layout << "start_table 4 $m_eou_level $m_eou_kills $m_eou_secrets $m_eou_time ";
- int y = 16;
- level_entry_t totals {};
- int32_t num_rows = 0;
- for (auto &entry : game.level_entries)
- {
- if (!*entry.map_name)
- break;
- G_EndOfUnitEntry(layout, y, entry);
- y += 8;
-
- totals.found_secrets += entry.found_secrets;
- totals.killed_monsters += entry.killed_monsters;
- totals.time += entry.time;
- totals.total_monsters += entry.total_monsters;
- totals.total_secrets += entry.total_secrets;
- if (entry.visit_order)
- num_rows++;
- }
- y += 8;
- // make this a space so it prints totals
- if (num_rows > 1)
- {
- layout << "table_row 0 "; // empty row to separate totals
- totals.pretty_name[0] = ' ';
- G_EndOfUnitEntry(layout, y, totals);
- }
- layout << "xv 160 yt 0 draw_table ";
- layout << "ifgef " << (level.intermission_server_frame + (5_sec).frames()) << " yb -48 xv 0 loc_cstring2 0 \"$m_eou_press_button\" endif ";
- gi.WriteByte(svc_layout);
- gi.WriteString(layout.str().c_str());
- gi.multicast(vec3_origin, MULTICAST_ALL, true);
- for (auto player : active_players())
- player->client->showeou = true;
- }
- // data is binary now.
- // u8 num_teams
- // u8 num_players
- // [ repeat num_teams:
- // string team_name
- // ]
- // [ repeat num_players:
- // u8 client_index
- // s32 score
- // u8 ranking
- // (if num_teams > 0)
- // u8 team
- // ]
- void G_ReportMatchDetails(bool is_end)
- {
- static std::array<uint32_t, MAX_CLIENTS> player_ranks;
- player_ranks = {};
- // CTF/TDM is simple
- if (ctf->integer || teamplay->integer)
- {
- CTFCalcRankings(player_ranks);
- gi.WriteByte(2);
- gi.WriteString("RED TEAM"); // team 0
- gi.WriteString("BLUE TEAM"); // team 1
- }
- else
- {
- // sort players by score, then match everybody to
- // the current highest score downwards until we run out of players.
- static std::array<edict_t *, MAX_CLIENTS> sorted_players;
- size_t num_active_players = 0;
- for (auto player : active_players())
- sorted_players[num_active_players++] = player;
- std::sort(sorted_players.begin(), sorted_players.begin() + num_active_players, [](const edict_t *a, const edict_t *b) { return b->client->resp.score < a->client->resp.score; });
- int32_t current_score = INT_MIN;
- int32_t current_rank = 0;
- for (size_t i = 0; i < num_active_players; i++)
- {
- if (!current_rank || sorted_players[i]->client->resp.score != current_score)
- {
- current_rank++;
- current_score = sorted_players[i]->client->resp.score;
- }
- player_ranks[sorted_players[i]->s.number - 1] = current_rank;
- }
- gi.WriteByte(0);
- }
- uint8_t num_players = 0;
- for (auto player : active_players())
- {
- // leave spectators out of this data, they don't need to be seen.
- if (player->client->pers.spawned && !player->client->resp.spectator)
- {
- // just in case...
- if (G_TeamplayEnabled() && player->client->resp.ctf_team == CTF_NOTEAM)
- continue;
- num_players++;
- }
- }
- gi.WriteByte(num_players);
- for (auto player : active_players())
- {
- // leave spectators out of this data, they don't need to be seen.
- if (player->client->pers.spawned && !player->client->resp.spectator)
- {
- // just in case...
- if (G_TeamplayEnabled() && player->client->resp.ctf_team == CTF_NOTEAM)
- continue;
- gi.WriteByte(player->s.number - 1);
- gi.WriteLong(player->client->resp.score);
- gi.WriteByte(player_ranks[player->s.number - 1]);
- if (G_TeamplayEnabled())
- gi.WriteByte(player->client->resp.ctf_team == CTF_TEAM1 ? 0 : 1);
- }
- }
- gi.ReportMatchDetails_Multicast(is_end);
- }
- void BeginIntermission(edict_t *targ)
- {
- edict_t *ent, *client;
- if (level.intermissiontime)
- return; // already activated
- // ZOID
- if (ctf->integer)
- CTFCalcScores();
- // ZOID
- game.autosaved = false;
- level.intermissiontime = level.time;
- // respawn any dead clients
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- client = g_edicts + 1 + i;
- if (!client->inuse)
- continue;
- if (client->health <= 0)
- {
- // give us our max health back since it will reset
- // to pers.health; in instanced items we'd lose the items
- // we touched so we always want to respawn with our max.
- if (P_UseCoopInstancedItems())
- client->client->pers.health = client->client->pers.max_health = client->max_health;
- respawn(client);
- }
- }
- level.intermission_server_frame = gi.ServerFrame();
- level.changemap = targ->map;
- level.intermission_clear = targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_CLEAR_INVENTORY);
- level.intermission_eou = false;
- level.intermission_fade = targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_FADE_OUT);
- // destroy all player trails
- PlayerTrail_Destroy(nullptr);
- // [Paril-KEX] update game level entry
- G_UpdateLevelEntry();
- if (strstr(level.changemap, "*"))
- {
- if (coop->integer)
- {
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- client = g_edicts + 1 + i;
- if (!client->inuse)
- continue;
- // strip players of all keys between units
- for (uint32_t n = 0; n < IT_TOTAL; n++)
- if (itemlist[n].flags & IF_KEY)
- client->client->pers.inventory[n] = 0;
- }
- }
- if (level.achievement && level.achievement[0])
- {
- gi.WriteByte(svc_achievement);
- gi.WriteString(level.achievement);
- gi.multicast(vec3_origin, MULTICAST_ALL, true);
- }
- level.intermission_eou = true;
- // "no end of unit" maps handle intermission differently
- if (!targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_NO_END_OF_UNIT))
- G_EndOfUnitMessage();
- else if (targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_IMMEDIATE_LEAVE) && !deathmatch->integer)
- {
- // Need to call this now
- G_ReportMatchDetails(true);
- level.exitintermission = 1; // go immediately to the next level
- return;
- }
- }
- else
- {
- if (!deathmatch->integer)
- {
- level.exitintermission = 1; // go immediately to the next level
- return;
- }
- }
- // Call while intermission is running
- G_ReportMatchDetails(true);
- level.exitintermission = 0;
- if (!level.level_intermission_set)
- {
- // find an intermission spot
- ent = G_FindByString<&edict_t::classname>(nullptr, "info_player_intermission");
- if (!ent)
- { // the map creator forgot to put in an intermission point...
- ent = G_FindByString<&edict_t::classname>(nullptr, "info_player_start");
- if (!ent)
- ent = G_FindByString<&edict_t::classname>(nullptr, "info_player_deathmatch");
- }
- else
- { // choose one of four spots
- int32_t i = irandom(4);
- while (i--)
- {
- ent = G_FindByString<&edict_t::classname>(ent, "info_player_intermission");
- if (!ent) // wrap around the list
- ent = G_FindByString<&edict_t::classname>(ent, "info_player_intermission");
- }
- }
- level.intermission_origin = ent->s.origin;
- level.intermission_angle = ent->s.angles;
- }
- // move all clients to the intermission point
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- client = g_edicts + 1 + i;
- if (!client->inuse)
- continue;
- MoveClientToIntermission(client);
- }
- }
- constexpr size_t MAX_SCOREBOARD_SIZE = 1024;
- /*
- ==================
- DeathmatchScoreboardMessage
- ==================
- */
- void DeathmatchScoreboardMessage(edict_t *ent, edict_t *killer)
- {
- static std::string entry, string;
- size_t j;
- int sorted[MAX_CLIENTS];
- int sortedscores[MAX_CLIENTS];
- int score;
- int x, y;
- gclient_t *cl;
- edict_t *cl_ent;
- const char *tag;
- // ZOID
- if (G_TeamplayEnabled())
- {
- CTFScoreboardMessage(ent, killer);
- return;
- }
- // ZOID
- entry.clear();
- string.clear();
- // sort the clients by score
- uint32_t total = 0;
- for (uint32_t i = 0; i < game.maxclients; i++)
- {
- cl_ent = g_edicts + 1 + i;
- if (!cl_ent->inuse || game.clients[i].resp.spectator)
- continue;
- score = game.clients[i].resp.score;
- for (j = 0; j < total; j++)
- {
- if (score > sortedscores[j])
- break;
- }
- for (uint32_t k = total; k > j; k--)
- {
- sorted[k] = sorted[k - 1];
- sortedscores[k] = sortedscores[k - 1];
- }
- sorted[j] = i;
- sortedscores[j] = score;
- total++;
- }
- // add the clients in sorted order
- if (total > 16)
- total = 16;
- for (uint32_t i = 0; i < total; i++)
- {
- cl = &game.clients[sorted[i]];
- cl_ent = g_edicts + 1 + sorted[i];
- x = (i >= 8) ? 130 : -72;
- y = 0 + 32 * (i % 8);
- // add a dogtag
- // [Paril-KEX] use dynamic dogtags
- tag = nullptr;
- //===============
- // ROGUE
- // allow new DM games to override the tag picture
- if (gamerules->integer)
- {
- if (DMGame.DogTag)
- DMGame.DogTag(cl_ent, killer, &tag);
- }
- // ROGUE
- //===============
- if (tag)
- {
- fmt::format_to(std::back_inserter(entry), FMT_STRING("xv {} yv {} picn {} "), x + 32, y, tag);
- if (string.length() + entry.length() > MAX_SCOREBOARD_SIZE)
- break;
- string += entry;
- }
- else
- {
- fmt::format_to(std::back_inserter(entry), FMT_STRING("xv {} yv {} dogtag {} "), x + 32, y, sorted[i]);
- if (string.length() + entry.length() > MAX_SCOREBOARD_SIZE)
- break;
- string += entry;
- }
- entry.clear();
- fmt::format_to(std::back_inserter(entry),
- FMT_STRING("client {} {} {} {} {} {} "),
- x, y, sorted[i], cl->resp.score, cl->ping, (int32_t) (level.time - cl->resp.entertime).minutes());
- if (string.length() + entry.length() > MAX_SCOREBOARD_SIZE)
- break;
- string += entry;
- entry.clear();
- }
- // [Paril-KEX] time & frags
- if (fraglimit->integer)
- {
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 $g_score_frags \"{}\" "), fraglimit->integer);
- }
- if (timelimit->value && !level.intermissiontime)
- {
- fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 time_limit {} "), gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms);
- }
- if (level.intermissiontime)
- fmt::format_to(std::back_inserter(string), FMT_STRING("ifgef {} yb -48 xv 0 loc_cstring2 0 \"$m_eou_press_button\" endif "), (level.intermission_server_frame + (5_sec).frames()));
- gi.WriteByte(svc_layout);
- gi.WriteString(string.c_str());
- }
- /*
- ==================
- DeathmatchScoreboard
- Draw instead of help message.
- Note that it isn't that hard to overflow the 1400 byte message limit!
- ==================
- */
- void DeathmatchScoreboard(edict_t *ent)
- {
- DeathmatchScoreboardMessage(ent, ent->enemy);
- gi.unicast(ent, true);
- ent->client->menutime = level.time + 3_sec;
- }
- /*
- ==================
- Cmd_Score_f
- Display the scoreboard
- ==================
- */
- void Cmd_Score_f(edict_t *ent)
- {
- if (level.intermissiontime)
- return;
- ent->client->showinventory = false;
- ent->client->showhelp = false;
- globals.server_flags &= ~SERVER_FLAG_SLOW_TIME;
- // ZOID
- if (ent->client->menu)
- PMenu_Close(ent);
- // ZOID
- if (!deathmatch->integer && !coop->integer)
- return;
- if (ent->client->showscores)
- {
- ent->client->showscores = false;
- ent->client->update_chase = true;
- return;
- }
- ent->client->showscores = true;
- DeathmatchScoreboard(ent);
- }
- /*
- ==================
- HelpComputer
- Draw help computer.
- ==================
- */
- void HelpComputer(edict_t *ent)
- {
- const char *sk;
- if (skill->integer == 0)
- sk = "$m_easy";
- else if (skill->integer == 1)
- sk = "$m_medium";
- else if (skill->integer == 2)
- sk = "$m_hard";
- else
- sk = "$m_nightmare";
- // send the layout
- std::string helpString = "";
- helpString += G_Fmt(
- "xv 32 yv 8 picn help " // background
- "xv 0 yv 25 cstring2 \"{}\" ", // level name
- level.level_name);
- if (level.is_n64)
- {
- helpString += G_Fmt("xv 0 yv 54 loc_cstring 1 \"{{}}\" \"{}\" ", // help 1
- game.helpmessage1);
- }
- else
- {
- int y = 54;
- if (strlen(game.helpmessage1))
- {
- helpString += G_Fmt("xv 0 yv {} loc_cstring2 0 \"$g_pc_primary_objective\" " // title
- "xv 0 yv {} loc_cstring 0 \"{}\" ",
- y,
- y + 11,
- game.helpmessage1);
- y += 58;
- }
- if (strlen(game.helpmessage2))
- {
- helpString += G_Fmt("xv 0 yv {} loc_cstring2 0 \"$g_pc_secondary_objective\" " // title
- "xv 0 yv {} loc_cstring 0 \"{}\" ",
- y,
- y + 11,
- game.helpmessage2);
- }
- }
- helpString += G_Fmt("xv 55 yv 164 loc_string2 0 \"{}\" "
- "xv 265 yv 164 loc_rstring2 1 \"{{}}: {}/{}\" \"$g_pc_goals\" "
- "xv 55 yv 172 loc_string2 1 \"{{}}: {}/{}\" \"$g_pc_kills\" "
- "xv 265 yv 172 loc_rstring2 1 \"{{}}: {}/{}\" \"$g_pc_secrets\" ",
- sk,
- level.found_goals, level.total_goals,
- level.killed_monsters, level.total_monsters,
- level.found_secrets, level.total_secrets);
- gi.WriteByte(svc_layout);
- gi.WriteString(helpString.c_str());
- gi.unicast(ent, true);
- }
- /*
- ==================
- Cmd_Help_f
- Display the current help message
- ==================
- */
- void Cmd_Help_f(edict_t *ent)
- {
- // this is for backwards compatability
- if (deathmatch->integer)
- {
- Cmd_Score_f(ent);
- return;
- }
- if (level.intermissiontime)
- return;
- ent->client->showinventory = false;
- ent->client->showscores = false;
- if (ent->client->showhelp &&
- (ent->client->pers.game_help1changed == game.help1changed ||
- ent->client->pers.game_help2changed == game.help2changed))
- {
- ent->client->showhelp = false;
- globals.server_flags &= ~SERVER_FLAG_SLOW_TIME;
- return;
- }
- ent->client->showhelp = true;
- ent->client->pers.helpchanged = 0;
- globals.server_flags |= SERVER_FLAG_SLOW_TIME;
- HelpComputer(ent);
- }
- //=======================================================================
- // [Paril-KEX] for stats we want to always be set in coop
- // even if we're spectating
- void G_SetCoopStats(edict_t *ent)
- {
- if (coop->integer && g_coop_enable_lives->integer)
- ent->client->ps.stats[STAT_LIVES] = ent->client->pers.lives + 1;
- else
- ent->client->ps.stats[STAT_LIVES] = 0;
- // stat for text on what we're doing for respawn
- if (ent->client->coop_respawn_state)
- ent->client->ps.stats[STAT_COOP_RESPAWN] = CONFIG_COOP_RESPAWN_STRING + (ent->client->coop_respawn_state - COOP_RESPAWN_IN_COMBAT);
- else
- ent->client->ps.stats[STAT_COOP_RESPAWN] = 0;
- }
- struct powerup_info_t
- {
- item_id_t item;
- gtime_t gclient_t::*time_ptr = nullptr;
- int32_t gclient_t::*count_ptr = nullptr;
- } powerup_table[] = {
- { IT_ITEM_QUAD, &gclient_t::quad_time },
- { IT_ITEM_QUADFIRE, &gclient_t::quadfire_time },
- { IT_ITEM_DOUBLE, &gclient_t::double_time },
- { IT_ITEM_INVULNERABILITY, &gclient_t::invincible_time },
- { IT_ITEM_INVISIBILITY, &gclient_t::invisible_time },
- { IT_ITEM_ENVIROSUIT, &gclient_t::enviro_time },
- { IT_ITEM_REBREATHER, &gclient_t::breather_time },
- { IT_ITEM_IR_GOGGLES, &gclient_t::ir_time },
- { IT_ITEM_SILENCER, nullptr, &gclient_t::silencer_shots }
- };
- /*
- ===============
- G_SetStats
- ===============
- */
- void G_SetStats(edict_t *ent)
- {
- gitem_t *item;
- item_id_t index;
- int cells = 0;
- item_id_t power_armor_type;
- unsigned int invIndex;
- //
- // health
- //
- if (ent->s.renderfx & RF_USE_DISGUISE)
- ent->client->ps.stats[STAT_HEALTH_ICON] = level.disguise_icon;
- else
- ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
- ent->client->ps.stats[STAT_HEALTH] = ent->health;
- //
- // weapons
- //
- uint32_t weaponbits = 0;
- for (invIndex = IT_WEAPON_GRAPPLE; invIndex <= IT_WEAPON_DISRUPTOR; invIndex++)
- {
- if (ent->client->pers.inventory[invIndex])
- {
- weaponbits |= 1 << GetItemByIndex((item_id_t) invIndex)->weapon_wheel_index;
- }
- }
- ent->client->ps.stats[STAT_WEAPONS_OWNED_1] = (weaponbits & 0xFFFF);
- ent->client->ps.stats[STAT_WEAPONS_OWNED_2] = (weaponbits >> 16);
- ent->client->ps.stats[STAT_ACTIVE_WHEEL_WEAPON] = (ent->client->newweapon ? ent->client->newweapon->weapon_wheel_index :
- ent->client->pers.weapon ? ent->client->pers.weapon->weapon_wheel_index :
- -1);
- ent->client->ps.stats[STAT_ACTIVE_WEAPON] = ent->client->pers.weapon ? ent->client->pers.weapon->weapon_wheel_index : -1;
- //
- // ammo
- //
- ent->client->ps.stats[STAT_AMMO_ICON] = 0;
- ent->client->ps.stats[STAT_AMMO] = 0;
- if (ent->client->pers.weapon && ent->client->pers.weapon->ammo)
- {
- item = GetItemByIndex(ent->client->pers.weapon->ammo);
- if (!G_CheckInfiniteAmmo(item))
- {
- ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex(item->icon);
- ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->pers.weapon->ammo];
- }
- }
-
- memset(&ent->client->ps.stats[STAT_AMMO_INFO_START], 0, sizeof(uint16_t) * NUM_AMMO_STATS);
- for (unsigned int ammoIndex = AMMO_BULLETS; ammoIndex < AMMO_MAX; ++ammoIndex)
- {
- gitem_t *ammo = GetItemByAmmo((ammo_t) ammoIndex);
- uint16_t val = G_CheckInfiniteAmmo(ammo) ? AMMO_VALUE_INFINITE : clamp(ent->client->pers.inventory[ammo->id], 0, AMMO_VALUE_INFINITE - 1);
- G_SetAmmoStat((uint16_t *) &ent->client->ps.stats[STAT_AMMO_INFO_START], ammo->ammo_wheel_index, val);
- }
- //
- // armor
- //
- power_armor_type = PowerArmorType(ent);
- if (power_armor_type)
- cells = ent->client->pers.inventory[IT_AMMO_CELLS];
- index = ArmorIndex(ent);
- if (power_armor_type && (!index || (level.time.milliseconds() % 3000) < 1500))
- { // flash between power armor and other armor icon
- ent->client->ps.stats[STAT_ARMOR_ICON] = power_armor_type == IT_ITEM_POWER_SHIELD ? gi.imageindex("i_powershield") : gi.imageindex("i_powerscreen");
- ent->client->ps.stats[STAT_ARMOR] = cells;
- }
- else if (index)
- {
- item = GetItemByIndex(index);
- ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex(item->icon);
- ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
- }
- else
- {
- ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
- ent->client->ps.stats[STAT_ARMOR] = 0;
- }
- //
- // pickup message
- //
- if (level.time > ent->client->pickup_msg_time)
- {
- ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
- ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
- }
- // owned powerups
- memset(&ent->client->ps.stats[STAT_POWERUP_INFO_START], 0, sizeof(uint16_t) * NUM_POWERUP_STATS);
- for (unsigned int powerupIndex = POWERUP_SCREEN; powerupIndex < POWERUP_MAX; ++powerupIndex)
- {
- gitem_t *powerup = GetItemByPowerup((powerup_t) powerupIndex);
- uint16_t val;
- switch (powerup->id)
- {
- case IT_ITEM_POWER_SCREEN:
- case IT_ITEM_POWER_SHIELD:
- if (!ent->client->pers.inventory[powerup->id])
- val = 0;
- else if (ent->flags & FL_POWER_ARMOR)
- val = 2;
- else
- val = 1;
- break;
- case IT_ITEM_FLASHLIGHT:
- if (!ent->client->pers.inventory[powerup->id])
- val = 0;
- else if (ent->flags & FL_FLASHLIGHT)
- val = 2;
- else
- val = 1;
- break;
- default:
- val = clamp(ent->client->pers.inventory[powerup->id], 0, 3);
- break;
- }
- G_SetPowerupStat((uint16_t *) &ent->client->ps.stats[STAT_POWERUP_INFO_START], powerup->powerup_wheel_index, val);
- }
- ent->client->ps.stats[STAT_TIMER_ICON] = 0;
- ent->client->ps.stats[STAT_TIMER] = 0;
- //
- // timers
- //
- // PGM
- if (ent->client->owned_sphere)
- {
- if (ent->client->owned_sphere->spawnflags == SPHERE_DEFENDER) // defender
- ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_defender");
- else if (ent->client->owned_sphere->spawnflags == SPHERE_HUNTER) // hunter
- ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_hunter");
- else if (ent->client->owned_sphere->spawnflags == SPHERE_VENGEANCE) // vengeance
- ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("p_vengeance");
- else // error case
- ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex("i_fixme");
- ent->client->ps.stats[STAT_TIMER] = ceil(ent->client->owned_sphere->wait - level.time.seconds());
- }
- else
- {
- powerup_info_t *best_powerup = nullptr;
- for (auto &powerup : powerup_table)
- {
- auto *powerup_time = powerup.time_ptr ? &(ent->client->*powerup.time_ptr) : nullptr;
- auto *powerup_count = powerup.count_ptr ? &(ent->client->*powerup.count_ptr) : nullptr;
- if (powerup_time && *powerup_time <= level.time)
- continue;
- else if (powerup_count && !*powerup_count)
- continue;
- if (!best_powerup)
- {
- best_powerup = &powerup;
- continue;
- }
-
- if (powerup_time && *powerup_time < ent->client->*best_powerup->time_ptr)
- {
- best_powerup = &powerup;
- continue;
- }
- else if (powerup_count && !best_powerup->time_ptr)
- {
- best_powerup = &powerup;
- continue;
- }
- }
- if (best_powerup)
- {
- int16_t value;
- if (best_powerup->count_ptr)
- value = (ent->client->*best_powerup->count_ptr);
- else
- value = ceil((ent->client->*best_powerup->time_ptr - level.time).seconds());
- ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex(GetItemByIndex(best_powerup->item)->icon);
- ent->client->ps.stats[STAT_TIMER] = value;
- }
- }
- // PGM
- //
- // selected item
- //
- ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
- if (ent->client->pers.selected_item == IT_NULL)
- ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
- else
- {
- ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex(itemlist[ent->client->pers.selected_item].icon);
- if (ent->client->pers.selected_item_time < level.time)
- ent->client->ps.stats[STAT_SELECTED_ITEM_NAME] = 0;
- }
- //
- // layouts
- //
- ent->client->ps.stats[STAT_LAYOUTS] = 0;
- if (deathmatch->integer)
- {
- if (ent->client->pers.health <= 0 || level.intermissiontime || ent->client->showscores)
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT;
- if (ent->client->showinventory && ent->client->pers.health > 0)
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INVENTORY;
- }
- else
- {
- if (ent->client->showscores || ent->client->showhelp || ent->client->showeou)
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT;
- if (ent->client->showinventory && ent->client->pers.health > 0)
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INVENTORY;
- if (ent->client->showhelp)
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_HELP;
- }
- if (level.intermissiontime || ent->client->awaiting_respawn)
- {
- if (ent->client->awaiting_respawn || (level.intermission_eou || level.is_n64 || (deathmatch->integer && level.intermissiontime)))
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_HIDE_HUD;
- // N64 always merges into one screen on level ends
- if (level.intermission_eou || level.is_n64 || (deathmatch->integer && level.intermissiontime))
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INTERMISSION;
- }
-
- if (level.story_active)
- ent->client->ps.stats[STAT_LAYOUTS] |= LAYOUTS_HIDE_CROSSHAIR;
- else
- ent->client->ps.stats[STAT_LAYOUTS] &= ~LAYOUTS_HIDE_CROSSHAIR;
- // [Paril-KEX] key display
- if (!deathmatch->integer)
- {
- int32_t key_offset = 0;
- player_stat_t stat = STAT_KEY_A;
-
- ent->client->ps.stats[STAT_KEY_A] =
- ent->client->ps.stats[STAT_KEY_B] =
- ent->client->ps.stats[STAT_KEY_C] = 0;
- // there's probably a way to do this in one pass but
- // I'm lazy
- std::array<item_id_t, IT_TOTAL> keys_held;
- size_t num_keys_held = 0;
- for (auto &item : itemlist)
- {
- if (!(item.flags & IF_KEY))
- continue;
- else if (!ent->client->pers.inventory[item.id])
- continue;
- keys_held[num_keys_held++] = item.id;
- }
- if (num_keys_held > 3)
- key_offset = (int32_t) (level.time.seconds() / 5);
- for (int32_t i = 0; i < min(num_keys_held, (size_t) 3); i++, stat = (player_stat_t) (stat + 1))
- ent->client->ps.stats[stat] = gi.imageindex(GetItemByIndex(keys_held[(i + key_offset) % num_keys_held])->icon);
- }
- //
- // frags
- //
- ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
- //
- // help icon / current weapon if not shown
- //
- if (ent->client->pers.helpchanged >= 1 && ent->client->pers.helpchanged <= 2 && (level.time.milliseconds() % 1000) < 500) // haleyjd: time-limited
- ent->client->ps.stats[STAT_HELPICON] = gi.imageindex("i_help");
- else if ((ent->client->pers.hand == CENTER_HANDED) && ent->client->pers.weapon)
- ent->client->ps.stats[STAT_HELPICON] = gi.imageindex(ent->client->pers.weapon->icon);
- else
- ent->client->ps.stats[STAT_HELPICON] = 0;
- ent->client->ps.stats[STAT_SPECTATOR] = 0;
- // set & run the health bar stuff
- for (size_t i = 0; i < MAX_HEALTH_BARS; i++)
- {
- byte *health_byte = reinterpret_cast<byte *>(&ent->client->ps.stats[STAT_HEALTH_BARS]) + i;
- if (!level.health_bar_entities[i])
- *health_byte = 0;
- else if (level.health_bar_entities[i]->timestamp)
- {
- if (level.health_bar_entities[i]->timestamp < level.time)
- {
- level.health_bar_entities[i] = nullptr;
- *health_byte = 0;
- continue;
- }
- *health_byte = 0b10000000;
- }
- else
- {
- // enemy dead
- if (!level.health_bar_entities[i]->enemy->inuse || level.health_bar_entities[i]->enemy->health <= 0)
- {
- // hack for Makron
- if (level.health_bar_entities[i]->enemy->monsterinfo.aiflags & AI_DOUBLE_TROUBLE)
- {
- *health_byte = 0b10000000;
- continue;
- }
- if (level.health_bar_entities[i]->delay)
- {
- level.health_bar_entities[i]->timestamp = level.time + gtime_t::from_sec(level.health_bar_entities[i]->delay);
- *health_byte = 0b10000000;
- }
- else
- {
- level.health_bar_entities[i] = nullptr;
- *health_byte = 0;
- }
-
- continue;
- }
- else if (level.health_bar_entities[i]->spawnflags.has(SPAWNFLAG_HEALTHBAR_PVS_ONLY) && !gi.inPVS(ent->s.origin, level.health_bar_entities[i]->enemy->s.origin, true))
- {
- *health_byte = 0;
- continue;
- }
- float health_remaining = ((float) level.health_bar_entities[i]->enemy->health) / level.health_bar_entities[i]->enemy->max_health;
- *health_byte = ((byte) (health_remaining * 0b01111111)) | 0b10000000;
- }
- }
- // ZOID
- SetCTFStats(ent);
- // ZOID
- }
- /*
- ===============
- G_CheckChaseStats
- ===============
- */
- void G_CheckChaseStats(edict_t *ent)
- {
- gclient_t *cl;
- for (uint32_t i = 1; i <= game.maxclients; i++)
- {
- cl = g_edicts[i].client;
- if (!g_edicts[i].inuse || cl->chase_target != ent)
- continue;
- cl->ps.stats = ent->client->ps.stats;
- G_SetSpectatorStats(g_edicts + i);
- }
- }
- /*
- ===============
- G_SetSpectatorStats
- ===============
- */
- void G_SetSpectatorStats(edict_t *ent)
- {
- gclient_t *cl = ent->client;
- if (!cl->chase_target)
- G_SetStats(ent);
- cl->ps.stats[STAT_SPECTATOR] = 1;
- // layouts are independant in spectator
- cl->ps.stats[STAT_LAYOUTS] = 0;
- if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores)
- cl->ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT;
- if (cl->showinventory && cl->pers.health > 0)
- cl->ps.stats[STAT_LAYOUTS] |= LAYOUTS_INVENTORY;
- if (cl->chase_target && cl->chase_target->inuse)
- cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS +
- (cl->chase_target - g_edicts) - 1;
- else
- cl->ps.stats[STAT_CHASE] = 0;
- }
|