From 8c098d581cf27a49297e0cc8adccb89ea88ac889 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Mon, 6 Oct 2025 21:59:51 +0100 Subject: [PATCH 001/142] Avoid stufftext disconnect for banned clients --- src/p_client.cpp | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index 7dcba26..854537f 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -3499,14 +3499,15 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia } } - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); - G_StuffCmd(ent, "disconnect\n"); - return true; - } - - // Model192 - if (!Q_strcasecmp(social_id, "Steamworks-76561197972296343")) { + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); + gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); + if (ent->client->pers.connected) + G_StuffCmd(ent, "disconnect\n"); + return true; + } + + // Model192 + if (!Q_strcasecmp(social_id, "Steamworks-76561197972296343")) { gi.Info_SetValueForKey(userinfo, "rejmsg", "WARNING! MOANERTONE DETECTED\n"); gentity_t *host = &g_entities[1]; @@ -3522,14 +3523,15 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia } } - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); - G_StuffCmd(ent, "disconnect\n"); - return true; - } + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); + gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); + if (ent->client->pers.connected) + G_StuffCmd(ent, "disconnect\n"); + return true; + } - // Dalude - if (!Q_strcasecmp(social_id, "Steamworks-76561199001991246") || !Q_strcasecmp(social_id, "EOS-07e230c273be4248bbf26c89033923c1")) { + // Dalude + if (!Q_strcasecmp(social_id, "Steamworks-76561199001991246") || !Q_strcasecmp(social_id, "EOS-07e230c273be4248bbf26c89033923c1")) { ent->client->sess.is_888 = true; gi.Info_SetValueForKey(userinfo, "rejmsg", "Fake 888 Agent detected!\n"); gi.Info_SetValueForKey(userinfo, "name", "Fake 888 Agent"); @@ -3546,11 +3548,12 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia gi.LocBroadcast_Print(PRINT_CHAT, "{}: bejesus, what a lovely lobby! certainly better than 888's!\n", name); } } - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); - G_StuffCmd(ent, "disconnect\n"); - return true; - } + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); + gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); + if (ent->client->pers.connected) + G_StuffCmd(ent, "disconnect\n"); + return true; + } return false; } From 73491ec322fadd697d64b3611ad3e8f2a7b5bcb0 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Mon, 6 Oct 2025 22:00:27 +0100 Subject: [PATCH 002/142] Allow EOS Dalude ban to trigger --- src/p_client.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index 7dcba26..079ec2d 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -3456,8 +3456,11 @@ gentity_t *ClientChooseSlot(const char *userinfo, const char *social_id, bool is } static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *social_id) { - // currently all bans are in Steamworks, don't bother if not from there - if (social_id[0] != 'S') + const bool has_steam_prefix = !Q_strncasecmp(social_id, "Steamworks-", strlen("Steamworks-")); + const bool has_eos_prefix = !Q_strncasecmp(social_id, "EOS-", strlen("EOS-")); + + // currently all bans are in Steamworks, don't bother if not from there (or EOS mirrors) + if (!has_steam_prefix && !has_eos_prefix) return false; // Israel @@ -3530,6 +3533,7 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia // Dalude if (!Q_strcasecmp(social_id, "Steamworks-76561199001991246") || !Q_strcasecmp(social_id, "EOS-07e230c273be4248bbf26c89033923c1")) { + gi.Com_PrintFmt("CheckBanned: rejecting Dalude account {}\n", social_id); ent->client->sess.is_888 = true; gi.Info_SetValueForKey(userinfo, "rejmsg", "Fake 888 Agent detected!\n"); gi.Info_SetValueForKey(userinfo, "name", "Fake 888 Agent"); From bcc1f0ae04fe8a1068c065ca50c571f0d8211879 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Mon, 6 Oct 2025 23:34:51 +0100 Subject: [PATCH 003/142] Enhance banned client handling and proximity mine feature - Modified `ClientCommand` to check for banned clients before processing commands, sending a message if banned. - Updated `T_Damage` to prevent damage to banned clients. - Added `is_banned` field to `client_session_t` for tracking banned status. - Introduced new functions and constants for proximity mine functionality, including explosion and activation logic. - Updated `G_AdjustPlayerScore` to prevent score changes for banned clients. - Ensured proper handling of banned clients in `ClientBegin`, `ClientConnect`, and `ClientThink`. - Enhanced `CheckBanned` to include specific checks for known banned accounts. - Modified `ClientUserinfoChanged` to handle updates for banned clients correctly. - Updated `ClientDisconnect` for proper cleanup on client disconnection. - Adjusted `ClientSpawn` to prevent respawning of banned clients. - Included checks for banned clients in `ClientInactivityTimer` and `ClientTimerActions`. - Improved readability and consistency in various inline functions. --- src/g_cmds.cpp | 17 +- src/g_combat.cpp | 2 +- src/g_local.h | 2 + src/g_misc.cpp | 102 ++++++ src/g_spawn.cpp | 2 + src/g_utils.cpp | 3 + src/g_weapon.cpp | 4 +- src/p_client.cpp | 788 +++++++++++++++++++++++++---------------------- 8 files changed, 549 insertions(+), 371 deletions(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index a4ebe3f..34a5451 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -3611,15 +3611,26 @@ void ClientCommand(gentity_t *ent) { cmds_t *cc; const char *cmd; + if (!ent->inuse) + return; // not fully in game yet + if (!ent->client) return; // not fully in game yet - // check if client is 888, print what is being sent and prevent any further processing - if (ent->client->sess.is_888) { - gi.Com_PrintFmt("Sneaky little snake Dalude/888 (%s) sent the following command:\n{}\n", ent->client->pers.netname, gi.args()); + if (ent->client->sess.is_banned && level.time > ent->client->sess.ban_msg_debounce_time) { + gi.Client_Print(ent, PRINT_HIGH, "You are banned from this mod, you naughty little sausage.\nYou should reflect on your behaviour towards other players.\n"); + + // ban message debounce time + ent->client->sess.ban_msg_debounce_time = level.time + 5_sec; return; } + // check if client is 888, print what is being sent and prevent any further processing + //if (ent->client->sess.is_888) { + // gi.Com_PrintFmt("Sneaky little snake Dalude/888 ({}) sent the following command:\n{}\n", ent->client->pers.netname, gi.args()); + // return; + //} + cmd = gi.argv(0); cc = FindClientCmdByName(cmd); diff --git a/src/g_combat.cpp b/src/g_combat.cpp index da1a365..bb16b06 100644 --- a/src/g_combat.cpp +++ b/src/g_combat.cpp @@ -717,7 +717,7 @@ void T_Damage(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, const } } - if (targ != attacker && attacker->client && targ->health > 0) { + if (targ != attacker && attacker->client && targ->health > 0 && !targ->client->sess.is_banned) { int stat_take = take; if (stat_take > targ->health) stat_take = targ->health; diff --git a/src/g_local.h b/src/g_local.h index fd9129c..908f428 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -3434,6 +3434,8 @@ struct client_session_t { bool admin; bool is_888; bool is_a_bot; + bool is_banned = false; + gtime_t ban_msg_debounce_time = 0_sec; // inactivity timer bool inactive; diff --git a/src/g_misc.cpp b/src/g_misc.cpp index 7436f1c..0c59496 100644 --- a/src/g_misc.cpp +++ b/src/g_misc.cpp @@ -2609,3 +2609,105 @@ void SP_misc_nuke_core(gentity_t *ent) { ent->use = misc_nuke_core_use; } + +/* +================= +misc_prox +================= +*/ + +constexpr gtime_t PROX_TIME_DELAY = 500_ms; +constexpr float PROX_DAMAGE_RADIUS = 192; +constexpr int32_t PROX_HEALTH = 20; +constexpr int32_t PROX_DAMAGE = 60; + +void Prox_Explode(gentity_t* ent); +void prox_die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int damage, const vec3_t& point, const mod_t& mod); + +THINK(misc_prox_seek) (gentity_t* ent) -> void +{ + gentity_t* target = nullptr; + gentity_t* best = nullptr; + vec3_t vec; + float len; + float oldlen = 8000; + + while ((target = findradius(target, ent->s.origin, PROX_DAMAGE_RADIUS)) != nullptr) { + if (target == ent) + continue; + + if (!target->client && !(target->monsterinfo.aiflags & AI_GOOD_GUY)) + continue; + + if (target->health <= 0) + continue; + + if (!visible(ent, target)) + continue; + + vec = ent->s.origin - target->s.origin; + len = vec.length(); + + if (!best) { + best = target; + oldlen = len; + continue; + } + if (len < oldlen) { + oldlen = len; + best = target; + } + } + + if (best) { + gi.sound(ent, CHAN_VOICE, gi.soundindex("weapons/proxwarn.wav"), 1, ATTN_NORM, 0); + ent->think = Prox_Explode; + ent->nextthink = level.time + PROX_TIME_DELAY; + return; + } + + ent->nextthink = level.time + 10_hz; +} + +THINK(misc_prox_activate) (gentity_t* ent) -> void +{ + gi.Com_Print("check 3!\n"); + ent->s.frame = 9; + ent->s.skinnum = 3; + ent->think = misc_prox_seek; + ent->nextthink = level.time + 10_hz; +} + +void SP_misc_prox(gentity_t* ent) +{ + gi.Com_Print("check 1!\n"); + if (deathmatch->integer) { + G_FreeEntity(ent); + return; + } + + if (!ent->health) + ent->health = PROX_HEALTH; + + if (!ent->dmg) + ent->dmg = PROX_DAMAGE; + + gi.Com_Print("check 2!\n"); + ent->s.modelindex = gi.modelindex("models/weapons/g_prox/tris.md2"); + ent->s.frame = 9; + ent->s.skinnum = 3; + ent->mins = { -6, -6, -6 }; + ent->maxs = { 6, 6, 6 }; + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->takedamage = true; + ent->die = prox_die; + ent->classname = "prox_mine"; + ent->flags |= (FL_DAMAGEABLE | FL_TRAP | FL_MECHANICAL); + ent->s.renderfx |= RF_IR_VISIBLE; + + ent->think = misc_prox_activate; + ent->nextthink = level.time + 1_sec; + + gi.linkentity(ent); +} diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 4d7d6c0..163e35a 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -172,6 +172,7 @@ void SP_misc_flare(gentity_t *ent); // [Sam-KEX] void SP_misc_hologram(gentity_t *ent); void SP_misc_lavaball(gentity_t *ent); void SP_misc_nuke_core(gentity_t *self); +void SP_misc_prox(gentity_t* self); void SP_monster_berserk(gentity_t *self); void SP_monster_gladiator(gentity_t *self); @@ -395,6 +396,7 @@ static const std::initializer_list spawns = { { "misc_transport", SP_misc_transport }, { "misc_nuke", SP_misc_nuke }, { "misc_nuke_core", SP_misc_nuke_core }, + { "misc_prox", SP_misc_prox }, { "monster_berserk", SP_monster_berserk }, { "monster_gladiator", SP_monster_gladiator }, diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 5205bf8..c90ce92 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -616,6 +616,9 @@ G_AdjustPlayerScore void G_AdjustPlayerScore(gclient_t *cl, int32_t offset, bool adjust_team, int32_t team_offset) { if (!cl) return; + if (cl->sess.is_banned) + return; + if (IsScoringDisabled()) return; diff --git a/src/g_weapon.cpp b/src/g_weapon.cpp index 4e39446..c7e782d 100644 --- a/src/g_weapon.cpp +++ b/src/g_weapon.cpp @@ -1746,7 +1746,7 @@ constexpr float PROX_DAMAGE_RADIUS = 192; constexpr int32_t PROX_HEALTH = 20; constexpr int32_t PROX_DAMAGE = 90; -static THINK(Prox_Explode) (gentity_t *ent) -> void { +THINK(Prox_Explode) (gentity_t *ent) -> void { vec3_t origin; gentity_t *owner; @@ -1781,7 +1781,7 @@ static THINK(Prox_Explode) (gentity_t *ent) -> void { G_FreeEntity(ent); } -static DIE(prox_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { +DIE(prox_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { // if set off by another prox, delay a little (chained explosions) if (strcmp(inflictor->classname, "prox_mine")) { self->takedamage = false; diff --git a/src/p_client.cpp b/src/p_client.cpp index 1e7197f..973d074 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -4,9 +4,9 @@ #include "monsters/m_player.h" #include "bots/bot_includes.h" -void SP_misc_teleporter_dest(gentity_t *ent); +void SP_misc_teleporter_dest(gentity_t* ent); -static THINK(info_player_start_drop) (gentity_t *self) -> void { +static THINK(info_player_start_drop) (gentity_t* self) -> void { // allow them to drop self->solid = SOLID_TRIGGER; self->movetype = MOVETYPE_TOSS; @@ -15,7 +15,7 @@ static THINK(info_player_start_drop) (gentity_t *self) -> void { gi.linkentity(self); } -static inline void deathmatch_spawn_flags(gentity_t *self) { +static inline void deathmatch_spawn_flags(gentity_t* self) { if (st.nobots) self->flags = FL_NO_BOTS; if (st.nohumans) @@ -28,7 +28,7 @@ The normal starting point for a level. "nobots" will prevent bots from using this spot. "nohumans" will prevent humans from using this spot. */ -void SP_info_player_start(gentity_t *self) { +void SP_info_player_start(gentity_t* self) { // fix stuck spawn points if (gi.trace(self->s.origin, PLAYER_MINS, PLAYER_MAXS, self->s.origin, self, MASK_SOLID).startsolid) G_FixStuckObject(self, self->s.origin); @@ -51,7 +51,7 @@ Targets will be fired when someone spawns in on them. "nobots" will prevent bots from using this spot. "nohumans" will prevent humans from using this spot. */ -void SP_info_player_deathmatch(gentity_t *self) { +void SP_info_player_deathmatch(gentity_t* self) { if (!deathmatch->integer) { G_FreeEntity(self); return; @@ -64,17 +64,17 @@ void SP_info_player_deathmatch(gentity_t *self) { /*QUAKED info_player_team_red (1 0 0) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP A potential Red Team spawning position for CTF games. */ -void SP_info_player_team_red(gentity_t *self) {} +void SP_info_player_team_red(gentity_t* self) {} /*QUAKED info_player_team_blue (0 0 1) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP A potential Blue Team spawning position for CTF games. */ -void SP_info_player_team_blue(gentity_t *self) {} +void SP_info_player_team_blue(gentity_t* self) {} /*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP A potential spawning position for coop games. */ -void SP_info_player_coop(gentity_t *self) { +void SP_info_player_coop(gentity_t* self) { if (!coop->integer) { G_FreeEntity(self); return; @@ -87,7 +87,7 @@ void SP_info_player_coop(gentity_t *self) { A potential spawning position for coop games on rmine2 where lava level needs to be checked. */ -void SP_info_player_coop_lava(gentity_t *self) { +void SP_info_player_coop_lava(gentity_t* self) { if (!coop->integer) { G_FreeEntity(self); return; @@ -102,12 +102,12 @@ void SP_info_player_coop_lava(gentity_t *self) { The deathmatch intermission point will be at one of these Use 'angles' instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch yaw roll' */ -void SP_info_player_intermission(gentity_t *ent) {} +void SP_info_player_intermission(gentity_t* ent) {} /*QUAKED info_ctf_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP Point trigger_teleports at these. */ -void SP_info_ctf_teleport_destination(gentity_t *ent) { +void SP_info_ctf_teleport_destination(gentity_t* ent) { ent->s.origin[2] += 16; } @@ -124,8 +124,8 @@ constexpr int8_t MAX_PLAYER_STOCK_MODELS = 3; constexpr int8_t MAX_PLAYER_STOCK_SKINS = 24; struct p_mods_skins_t { - const char *mname; // first model will be default model - const char *sname[MAX_PLAYER_STOCK_SKINS]; //index 0 will be default skin + const char* mname; // first model will be default model + const char* sname[MAX_PLAYER_STOCK_SKINS]; //index 0 will be default skin }; p_mods_skins_t original_models[MAX_PLAYER_STOCK_MODELS] = { @@ -167,7 +167,7 @@ p_mods_skins_t original_models[MAX_PLAYER_STOCK_MODELS] = { } }; -static const char *ClientSkinOverride(const char *s) { +static const char* ClientSkinOverride(const char* s) { if (g_allow_custom_skins->integer) { //gi.Com_PrintFmt("{}: returning {}\n", __FUNCTION__, s); @@ -239,19 +239,19 @@ static void PCfg_WriteConfig(gentity_t *ent) { gi.Com_PrintFmt("Player config written to: \"{}\"\n", name); } */ -static void PCfg_ClientInitPConfig(gentity_t *ent) { +static void PCfg_ClientInitPConfig(gentity_t* ent) { bool file_exists = false; bool cfg_valid = true; - + if (!ent->client) return; if (ent->svflags & SVF_BOT) return; // load up file - const char *name = G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id).data(); + const char* name = G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id).data(); - FILE *f = fopen(name, "rb"); + FILE* f = fopen(name, "rb"); if (f != NULL) { - char *buffer = nullptr; + char* buffer = nullptr; size_t length; size_t read_length; @@ -263,7 +263,7 @@ static void PCfg_ClientInitPConfig(gentity_t *ent) { cfg_valid = false; } if (cfg_valid) { - buffer = (char *)gi.TagMalloc(length + 1, '\0'); + buffer = (char*)gi.TagMalloc(length + 1, '\0'); if (length) { read_length = fread(buffer, 1, length, f); @@ -285,23 +285,25 @@ static void PCfg_ClientInitPConfig(gentity_t *ent) { if (!file_exists) { f = fopen(name, "w"); if (f) { - const char *str = G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname).data(); + const char* str = G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname).data(); fwrite(str, 1, strlen(str), f); gi.Com_PrintFmt("{}: Player config written to: \"{}\"\n", __FUNCTION__, name); fclose(f); - } else { + } + else { gi.Com_PrintFmt("{}: Cannot save player config: {}\n", __FUNCTION__, name); } - } else { + } + else { //gi.Com_PrintFmt("{}: Player config not saved as file already exists: \"{}\"\n", __FUNCTION__, name); } } //======================================================================= struct mon_name_t { - const char *classname; - const char *longname; + const char* classname; + const char* longname; }; mon_name_t monname[] = { { "monster_arachnid", "Arachnid" }, @@ -348,7 +350,7 @@ mon_name_t monname[] = { { "monster_widow2", "Black Widow" }, }; -static const char *MonsterName(const char *classname) { +static const char* MonsterName(const char* classname) { for (size_t i = 0; i < ARRAY_LEN(monname); i++) { if (!Q_strncasecmp(classname, monname[i].classname, strlen(classname))) return monname[i].longname; @@ -365,8 +367,8 @@ static bool IsVowel(const char c) { return false; } -static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, mod_t mod) { - const char *base = nullptr; +static void ClientObituary(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, mod_t mod) { + const char* base = nullptr; if (InCoopStyle() && attacker->client) mod.friendly_fire = true; @@ -472,7 +474,7 @@ static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *att return; if (attacker->svflags & SVF_MONSTER) { - const char *monname = MonsterName(attacker->classname); + const char* monname = MonsterName(attacker->classname); if (monname) gi.LocBroadcast_Print(PRINT_MEDIUM, "{} was killed by a{} {}\n", self->client->resp.netname, IsVowel(monname[0]) ? "n" : "", monname); @@ -481,7 +483,7 @@ static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *att if (!attacker->client) return; - + switch (mod.id) { case MOD_BLASTER: base = "$g_mod_kill_blaster"; @@ -603,8 +605,8 @@ static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *att // if at start and same team, clear. // [Paril-KEX] moved here so it's not an outlier in player_die. if (mod.id == MOD_TELEFRAG_SPAWN && - self->client->resp.ctf_state < 2 && - self->client->sess.team == attacker->client->sess.team) { + self->client->resp.ctf_state < 2 && + self->client->sess.team == attacker->client->sess.team) { self->client->resp.ctf_state = 0; return; } @@ -615,10 +617,12 @@ static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *att if (!(self->svflags & SVF_BOT)) { if (level.match_state == matchst_t::MATCH_WARMUP_READYUP) { BroadcastReadyReminderMessage(); - } else { + } + else { if (GTF(GTF_ROUNDS) && GTF(GTF_ELIMINATION) && level.round_state == roundst_t::ROUND_IN_PROGRESS) { gi.LocClient_Print(self, PRINT_CENTER, "You were fragged by {}\nYou will respawn next round.", attacker->client->resp.netname); - } else if (GT(GT_FREEZE) && level.round_state == roundst_t::ROUND_IN_PROGRESS) { + } + else if (GT(GT_FREEZE) && level.round_state == roundst_t::ROUND_IN_PROGRESS) { bool last_standing = true; if (self->client->sess.team == TEAM_RED && level.num_living_red > 1 || self->client->sess.team == TEAM_BLUE && level.num_living_blue > 1) @@ -626,7 +630,8 @@ static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *att gi.LocClient_Print(self, PRINT_CENTER, "You were frozen by {}{}", attacker->client->resp.netname, last_standing ? "" : "\nYou will respawn once thawed."); - } else { + } + else { gi.LocClient_Print(self, PRINT_CENTER, "You were {} by {}", GT(GT_FREEZE) ? "frozen" : "fragged", attacker->client->resp.netname); } } @@ -634,28 +639,33 @@ static void ClientObituary(gentity_t *self, gentity_t *inflictor, gentity_t *att if (!(attacker->svflags & SVF_BOT)) { if (Teams() && OnSameTeam(self, attacker)) { gi.LocClient_Print(attacker, PRINT_CENTER, "You fragged {}, your team mate :(", self->client->resp.netname); - } else { + } + else { if (level.match_state == matchst_t::MATCH_WARMUP_READYUP) { BroadcastReadyReminderMessage(); - } else if (attacker->client->resp.kill_count && !(attacker->client->resp.kill_count % 10)) { + } + else if (attacker->client->resp.kill_count && !(attacker->client->resp.kill_count % 10)) { gi.LocBroadcast_Print(PRINT_CENTER, "{} is on a rampage\nwith {} frags!", attacker->client->resp.netname, attacker->client->resp.kill_count); AnnouncerSound(attacker, "rampage1", nullptr, false); attacker->client->pers.medal_time = level.time; attacker->client->pers.medal_type = MEDAL_RAMPAGE; attacker->client->pers.medal_count[MEDAL_RAMPAGE]++; - } else if (kill_count >= 10) { + } + else if (kill_count >= 10) { gi.LocBroadcast_Print(PRINT_CENTER, "{} put an end to {}'s\nrampage!", attacker->client->resp.netname, self->client->resp.netname); - } else if (Teams() || level.match_state != matchst_t::MATCH_IN_PROGRESS) { + } + else if (Teams() || level.match_state != matchst_t::MATCH_IN_PROGRESS) { if (attacker->client->sess.pc.show_fragmessages) gi.LocClient_Print(attacker, PRINT_CENTER, "You {} {}", GT(GT_FREEZE) ? "froze" : "fragged", self->client->resp.netname); - } else { + } + else { if (attacker->client->sess.pc.show_fragmessages) gi.LocClient_Print(attacker, PRINT_CENTER, "You {} {}\n{} place with {}", GT(GT_FREEZE) ? "froze" : "fragged", self->client->resp.netname, G_PlaceString(attacker->client->resp.rank + 1), attacker->client->resp.score); } } if (attacker->client->sess.pc.killbeep_num > 0 && attacker->client->sess.pc.killbeep_num < 5) { - const char *sb[5] = { "", "nav_editor/select_node.wav", "misc/comp_up.wav", "insane/insane7.wav", "nav_editor/finish_node_move.wav" }; + const char* sb[5] = { "", "nav_editor/select_node.wav", "misc/comp_up.wav", "insane/insane7.wav", "nav_editor/finish_node_move.wav" }; gi.local_sound(attacker, CHAN_AUTO, gi.soundindex(sb[attacker->client->sess.pc.killbeep_num]), 1, ATTN_NONE, 0); } } @@ -674,7 +684,7 @@ TossClientItems Toss the weapon, tech, CTF flag and powerups for the killed player ================= */ -static void TossClientItems(gentity_t *self) { +static void TossClientItems(gentity_t* self) { if (!deathmatch->integer) return; @@ -685,8 +695,8 @@ static void TossClientItems(gentity_t *self) { if (IsCombatDisabled()) return; - gitem_t *wp; - gentity_t *drop; + gitem_t* wp; + gentity_t* drop; bool quad, doubled, haste, protection, invis, regen; // drop weapon @@ -853,14 +863,16 @@ static void TossClientItems(gentity_t *self) { LookAtKiller ================== */ -void LookAtKiller(gentity_t *self, gentity_t *inflictor, gentity_t *attacker) { +void LookAtKiller(gentity_t* self, gentity_t* inflictor, gentity_t* attacker) { vec3_t dir; if (attacker && attacker != world && attacker != self) { dir = attacker->s.origin - self->s.origin; - } else if (inflictor && inflictor != world && inflictor != self) { + } + else if (inflictor && inflictor != world && inflictor != self) { dir = inflictor->s.origin - self->s.origin; - } else { + } + else { self->client->killer_yaw = self->s.angles[YAW]; return; } @@ -902,7 +914,7 @@ static bool Match_CanScore() { player_die ================== */ -DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { +DIE(player_die) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void { if (self->client->ps.pmove.pm_type == PM_DEAD) return; @@ -929,7 +941,8 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if (!mod.no_point_loss) G_AdjustPlayerScore(attacker->client, -1, GT(GT_TDM), -1); attacker->client->resp.kill_count = 0; - } else { + } + else { G_AdjustPlayerScore(attacker->client, 1, GT(GT_TDM), 1); if (attacker->health > 0) attacker->client->resp.kill_count++; @@ -965,7 +978,8 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int } } } - } else { + } + else { if (!mod.no_point_loss) G_AdjustPlayerScore(self->client, -1, GT(GT_TDM), -1); } @@ -985,7 +999,8 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if (false) { // Race mode removed self->client->respawn_min_time = self->client->respawn_time = level.time; - } else { + } + else { self->client->respawn_min_time = (level.time + gtime_t::from_sec(g_dm_respawn_delay_min->value)); if (deathmatch->integer && g_dm_force_respawn_time->integer) { self->client->respawn_time = (level.time + gtime_t::from_sec(g_dm_force_respawn_time->value)); @@ -1033,7 +1048,7 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int // vengeance and hunter will die if they're not attacking, // defender should always die if (self->client->owned_sphere) { - gentity_t *sphere; + gentity_t* sphere; sphere = self->client->owned_sphere; sphere->die(sphere, self, self, 0, vec3_origin, mod); @@ -1048,7 +1063,8 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int if (GT(GT_FREEZE) && !level.intermission_time && self->client->eliminated && !self->client->resp.thawer) { self->s.effects |= EF_COLOR_SHELL; self->s.renderfx |= (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE); - } else { + } + else { self->s.effects = EF_NONE; self->s.renderfx = RF_NONE; } @@ -1082,23 +1098,26 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->flags &= ~FL_NOGIB; ThrowClientHead(self, damage); - + self->client->anim_priority = ANIM_DEATH; self->client->anim_end = 0; - + self->takedamage = false; - } else { // normal death + } + else { // normal death if (!self->deadflag) { if (GT(GT_FREEZE)) { self->s.frame = FRAME_crstnd01 - 1; self->client->anim_end = self->s.frame; - } else { + } + else { // start a death animation self->client->anim_priority = ANIM_DEATH; if (self->client->ps.pmove.pm_flags & PMF_DUCKED) { self->s.frame = FRAME_crdeath1 - 1; self->client->anim_end = FRAME_crdeath5; - } else { + } + else { switch (irandom(3)) { case 0: self->s.frame = FRAME_death101 - 1; @@ -1115,7 +1134,7 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int } } } - static constexpr const char *death_sounds[] = { + static constexpr const char* death_sounds[] = { "*death1.wav", "*death2.wav", "*death3.wav", @@ -1169,20 +1188,20 @@ DIE(player_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int #include // [Paril-KEX] -static void Player_GiveStartItems(gentity_t *ent, const char *ptr) { +static void Player_GiveStartItems(gentity_t* ent, const char* ptr) { char token_copy[MAX_TOKEN_CHARS]; - const char *token; + const char* token; while (*(token = COM_ParseEx(&ptr, ";"))) { Q_strlcpy(token_copy, token, sizeof(token_copy)); - const char *ptr_copy = token_copy; + const char* ptr_copy = token_copy; - const char *item_name = COM_Parse(&ptr_copy); - gitem_t *item = FindItemByClassname(item_name); + const char* item_name = COM_Parse(&ptr_copy); + gitem_t* item = FindItemByClassname(item_name); if (!item || !item->pickup) continue; - //gi.Com_ErrorFmt("Invalid g_start_item entry: {}\n", item_name); + //gi.Com_ErrorFmt("Invalid g_start_item entry: {}\n", item_name); int32_t count = 1; @@ -1194,7 +1213,7 @@ static void Player_GiveStartItems(gentity_t *ent, const char *ptr) { continue; } - gentity_t *dummy = G_Spawn(); + gentity_t* dummy = G_Spawn(); dummy->item = item; dummy->count = count; dummy->spawnflags |= SPAWNFLAG_ITEM_DROPPED; @@ -1211,7 +1230,7 @@ This is only called when the game first initializes in single player, but is called after each death and level change in deathmatch ============== */ -void InitClientPersistant(gentity_t *ent, gclient_t *client) { +void InitClientPersistant(gentity_t* ent, gclient_t* client) { // backup & restore userinfo char userinfo[MAX_INFO_STRING]; Q_strlcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); @@ -1241,7 +1260,8 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { if (GTF(GTF_ARENA)) { health = clamp(g_arena_start_health->integer, 1, 9999); armor = clamp(g_arena_start_armor->integer, 0, 999); - } else { + } + else { health = clamp(g_starting_health->integer, 1, 9999); armor = clamp(g_starting_armor->integer, 0, 999); } @@ -1273,8 +1293,8 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { if (coop->integer) { for (auto player : active_clients()) { if (player == ent || !player->client->pers.spawned || - !ClientIsPlaying(player->client) || - player->movetype == MOVETYPE_NOCLIP || player->movetype == MOVETYPE_FREECAM) + !ClientIsPlaying(player->client) || + player->movetype == MOVETYPE_NOCLIP || player->movetype == MOVETYPE_FREECAM) continue; client->pers.inventory = player->client->pers.inventory; @@ -1285,15 +1305,21 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { } } - if (GT(GT_BALL)) { + if (ent->client->sess.is_banned) { + client->pers.inventory.fill(0); + client->pers.health = 1; + } else if (GT(GT_BALL)) { client->pers.inventory[IT_WEAPON_CHAINFIST] = 1; - } else if (!taken_loadout) { + } + else if (!taken_loadout) { if (g_instagib->integer) { client->pers.inventory[IT_WEAPON_RAILGUN] = 1; client->pers.inventory[IT_AMMO_SLUGS] = AMMO_INFINITE; - } else if (g_nadefest->integer) { + } + else if (g_nadefest->integer) { client->pers.inventory[IT_AMMO_GRENADES] = AMMO_INFINITE; - } else if (GTF(GTF_ARENA)) { + } + else if (GTF(GTF_ARENA)) { client->pers.max_ammo.fill(50); client->pers.max_ammo[AMMO_SHELLS] = 50; client->pers.max_ammo[AMMO_BULLETS] = 300; @@ -1331,7 +1357,8 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { client->pers.inventory[IT_WEAPON_PLASMABEAM] = 1; if (!(RS(RS_Q1))) client->pers.inventory[IT_WEAPON_RAILGUN] = 1; - } else { + } + else { if (RS(RS_Q3A)) { client->pers.max_ammo.fill(200); client->pers.max_ammo[AMMO_BULLETS] = 200; @@ -1346,7 +1373,8 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { client->pers.inventory[IT_WEAPON_CHAINFIST] = 1; client->pers.inventory[IT_WEAPON_MACHINEGUN] = 1; client->pers.inventory[IT_AMMO_BULLETS] = (GT(GT_TDM)) ? 50 : 100; - } else if (RS(RS_Q1)) { + } + else if (RS(RS_Q1)) { client->pers.max_ammo.fill(200); client->pers.max_ammo[AMMO_BULLETS] = 200; client->pers.max_ammo[AMMO_SHELLS] = 200; @@ -1360,7 +1388,8 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { client->pers.inventory[IT_WEAPON_CHAINFIST] = 1; client->pers.inventory[IT_WEAPON_SHOTGUN] = 1; client->pers.inventory[IT_AMMO_SHELLS] = 10; - } else { + } + else { // fill with 50s, since it's our most common value client->pers.max_ammo.fill(50); client->pers.max_ammo[AMMO_BULLETS] = 200; @@ -1386,7 +1415,7 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { client->pers.inventory[i] = 1; - gitem_t *ammo = GetItemByIndex(itemlist[i].ammo); + gitem_t* ammo = GetItemByIndex(itemlist[i].ammo); if (ammo) Add_Ammo(&g_entities[client - game.clients + 1], ammo, InfiniteAmmoOn(ammo) ? AMMO_INFINITE : ammo->quantity * 2); @@ -1433,7 +1462,7 @@ void InitClientPersistant(gentity_t *ent, gclient_t *client) { client->pers.spawned = true; } -static void InitClientResp(gclient_t *cl) { +static void InitClientResp(gclient_t* cl) { bool showed_help = cl->resp.showed_help; team_t team = cl->sess.team; int motd_mod_count = cl->resp.motd_mod_count; @@ -1449,7 +1478,7 @@ static void InitClientResp(gclient_t *cl) { cl->resp.entertime = level.time; cl->resp.coop_respawn = cl->pers; - + cl->resp.motd_mod_count = motd_mod_count; cl->sess.team = team; @@ -1466,7 +1495,7 @@ gentities are wiped. ================== */ void SaveClientData() { - gentity_t *ent; + gentity_t* ent; for (size_t i = 0; i < game.maxclients; i++) { ent = &g_entities[1 + i]; @@ -1480,7 +1509,7 @@ void SaveClientData() { } } -void FetchClientEntData(gentity_t *ent) { +void FetchClientEntData(gentity_t* ent) { ent->health = ent->client->pers.health; ent->max_health = ent->client->pers.max_health; ent->flags |= ent->client->pers.saved_flags; @@ -1504,7 +1533,7 @@ Returns the distance to the nearest player from the given spot muffmode: excludes current client ================ */ -static float PlayersRangeFromSpot(gentity_t *ent, gentity_t *spot) { +static float PlayersRangeFromSpot(gentity_t* ent, gentity_t* spot) { float bestplayerdistance; vec3_t v; float playerdistance; @@ -1529,15 +1558,15 @@ static float PlayersRangeFromSpot(gentity_t *ent, gentity_t *spot) { return bestplayerdistance; } -static bool SpawnPointClear(gentity_t *spot) { +static bool SpawnPointClear(gentity_t* spot) { vec3_t p = spot->s.origin + vec3_t{ 0, 0, 9.f }; return !gi.trace(p, PLAYER_MINS, PLAYER_MAXS, p, spot, CONTENTS_PLAYER | CONTENTS_MONSTER).startsolid; } -select_spawn_result_t SelectDeathmatchSpawnPoint(gentity_t *ent, vec3_t avoid_point, playerspawn_t mode, bool force_spawn, bool fallback_to_ctf_or_start, bool intermission, bool initial) { +select_spawn_result_t SelectDeathmatchSpawnPoint(gentity_t* ent, vec3_t avoid_point, playerspawn_t mode, bool force_spawn, bool fallback_to_ctf_or_start, bool intermission, bool initial) { float cv_dist = g_dm_respawn_point_min_dist->value; struct spawn_point_t { - gentity_t *point; + gentity_t* point; float dist; }; @@ -1546,7 +1575,7 @@ select_spawn_result_t SelectDeathmatchSpawnPoint(gentity_t *ent, vec3_t avoid_po spawn_points.clear(); // gather all spawn points - gentity_t *spot = nullptr; + gentity_t* spot = nullptr; if (cv_dist > 512) cv_dist = 512; else if (cv_dist < 0) cv_dist = 0; @@ -1582,7 +1611,8 @@ select_spawn_result_t SelectDeathmatchSpawnPoint(gentity_t *ent, vec3_t avoid_po if (spawn_points.size() == 0) return { nullptr, false }; } - } else + } + else return { nullptr, false }; } } @@ -1596,125 +1626,125 @@ select_spawn_result_t SelectDeathmatchSpawnPoint(gentity_t *ent, vec3_t avoid_po } // order by distances ascending (top of list has closest players to point) - std::sort(spawn_points.begin(), spawn_points.end(), [](const spawn_point_t &a, const spawn_point_t &b) { return a.dist < b.dist; }); + std::sort(spawn_points.begin(), spawn_points.end(), [](const spawn_point_t& a, const spawn_point_t& b) { return a.dist < b.dist; }); switch (mode) { default: // high random case playerspawn_t::SPAWN_FAR_HALF: // farthest half - { - size_t margin = spawn_points.size() / 2; + { + size_t margin = spawn_points.size() / 2; - // for random, select a random point other than the two - // that are closest to the player if possible. - // shuffle the non-distance-related spawn points - std::shuffle(spawn_points.begin() + margin, spawn_points.end(), mt_rand); + // for random, select a random point other than the two + // that are closest to the player if possible. + // shuffle the non-distance-related spawn points + std::shuffle(spawn_points.begin() + margin, spawn_points.end(), mt_rand); - // run down the list and pick the first one that we can use - for (auto it = spawn_points.begin() + margin; it != spawn_points.end(); ++it) { - auto spot = it->point; + // run down the list and pick the first one that we can use + for (auto it = spawn_points.begin() + margin; it != spawn_points.end(); ++it) { + auto spot = it->point; - if (avoid_point == spot->s.origin) + if (avoid_point == spot->s.origin) + continue; + //muff: avoid respawning at or close to last spawn point + if (avoid_point && cv_dist) { + vec3_t v = spot->s.origin - avoid_point; + float d = v.length(); + + if (d <= cv_dist) { + if (g_dm_respawn_point_min_dist_debug->integer) + gi.Com_PrintFmt("{}: avoiding spawn point\n", *spot); continue; - //muff: avoid respawning at or close to last spawn point - if (avoid_point && cv_dist) { - vec3_t v = spot->s.origin - avoid_point; - float d = v.length(); - - if (d <= cv_dist) { - if (g_dm_respawn_point_min_dist_debug->integer) - gi.Com_PrintFmt("{}: avoiding spawn point\n", *spot); - continue; - } } - - if (ent && ent->client) { - if (ent->client->sess.is_a_bot) - if (spot->flags & FL_NO_BOTS) - continue; - if (!ent->client->sess.is_a_bot) - if (spot->flags & FL_NO_HUMANS) - continue; - } - - if (SpawnPointClear(spot)) - return { spot, true }; } - // none clear, so we have to pick one of the other two - if (SpawnPointClear(spawn_points[1].point)) - return { spawn_points[1].point, true }; - else if (SpawnPointClear(spawn_points[0].point)) - return { spawn_points[0].point, true }; - - break; - } - case playerspawn_t::SPAWN_FARTHEST: // farthest - { - size_t count = spawn_points.end() - spawn_points.begin(); - size_t size = spawn_points.size(); - //gi.Com_PrintFmt("count:{} size:{}\n", count, size); - for (int32_t i = spawn_points.size() - 1; i >= 0; --i) { - //muff: avoid respawning at or close to last spawn point - if (avoid_point && cv_dist) { - vec3_t v = spawn_points[i].point->s.origin - avoid_point; - float d = v.length(); - - if (d <= cv_dist) { - if (g_dm_respawn_point_min_dist_debug->integer) - gi.Com_PrintFmt("{}: avoiding spawn point\n", *spawn_points[i].point); - continue; - } - } - + if (ent && ent->client) { if (ent->client->sess.is_a_bot) if (spot->flags & FL_NO_BOTS) continue; if (!ent->client->sess.is_a_bot) if (spot->flags & FL_NO_HUMANS) continue; + } - if (SpawnPointClear(spawn_points[i].point)) - return { spawn_points[i].point, true }; + if (SpawnPointClear(spot)) + return { spot, true }; + } + + // none clear, so we have to pick one of the other two + if (SpawnPointClear(spawn_points[1].point)) + return { spawn_points[1].point, true }; + else if (SpawnPointClear(spawn_points[0].point)) + return { spawn_points[0].point, true }; + + break; + } + case playerspawn_t::SPAWN_FARTHEST: // farthest + { + size_t count = spawn_points.end() - spawn_points.begin(); + size_t size = spawn_points.size(); + //gi.Com_PrintFmt("count:{} size:{}\n", count, size); + for (int32_t i = spawn_points.size() - 1; i >= 0; --i) { + //muff: avoid respawning at or close to last spawn point + if (avoid_point && cv_dist) { + vec3_t v = spawn_points[i].point->s.origin - avoid_point; + float d = v.length(); + + if (d <= cv_dist) { + if (g_dm_respawn_point_min_dist_debug->integer) + gi.Com_PrintFmt("{}: avoiding spawn point\n", *spawn_points[i].point); + continue; + } } - // none clear, so we have to pick one of the other two - if (SpawnPointClear(spawn_points[1].point)) - return { spawn_points[1].point, true }; - else if (SpawnPointClear(spawn_points[0].point)) - return { spawn_points[0].point, true }; - break; + if (ent->client->sess.is_a_bot) + if (spot->flags & FL_NO_BOTS) + continue; + if (!ent->client->sess.is_a_bot) + if (spot->flags & FL_NO_HUMANS) + continue; + + if (SpawnPointClear(spawn_points[i].point)) + return { spawn_points[i].point, true }; } + // none clear, so we have to pick one of the other two + if (SpawnPointClear(spawn_points[1].point)) + return { spawn_points[1].point, true }; + else if (SpawnPointClear(spawn_points[0].point)) + return { spawn_points[0].point, true }; + + break; + } case playerspawn_t::SPAWN_NEAREST: // nearest - { - size_t count = spawn_points.end() - spawn_points.begin(); - size_t size = spawn_points.size(); - //gi.Com_PrintFmt("count:{} size:{}\n", count, size); - for (int32_t i = 0; i < spawn_points.size(); i++) { - //muff: avoid respawning at or close to last spawn point - if (avoid_point && cv_dist) { - vec3_t v = spawn_points[i].point->s.origin - avoid_point; - float d = v.length(); - - if (d <= cv_dist) { - if (g_dm_respawn_point_min_dist_debug->integer) - gi.Com_PrintFmt("{}: avoiding spawn point.\n", *spawn_points[i].point); - continue; - } + { + size_t count = spawn_points.end() - spawn_points.begin(); + size_t size = spawn_points.size(); + //gi.Com_PrintFmt("count:{} size:{}\n", count, size); + for (int32_t i = 0; i < spawn_points.size(); i++) { + //muff: avoid respawning at or close to last spawn point + if (avoid_point && cv_dist) { + vec3_t v = spawn_points[i].point->s.origin - avoid_point; + float d = v.length(); + + if (d <= cv_dist) { + if (g_dm_respawn_point_min_dist_debug->integer) + gi.Com_PrintFmt("{}: avoiding spawn point.\n", *spawn_points[i].point); + continue; } + } - if (ent->client->sess.is_a_bot) - if (spot->flags & FL_NO_BOTS) - continue; - if (!ent->client->sess.is_a_bot) - if (spot->flags & FL_NO_HUMANS) - continue; + if (ent->client->sess.is_a_bot) + if (spot->flags & FL_NO_BOTS) + continue; + if (!ent->client->sess.is_a_bot) + if (spot->flags & FL_NO_HUMANS) + continue; - if (SpawnPointClear(spawn_points[i].point)) - return { spawn_points[i].point, true }; - } - // none clear - break; + if (SpawnPointClear(spawn_points[i].point)) + return { spawn_points[i].point, true }; } + // none clear + break; + } } if (force_spawn) @@ -1747,7 +1777,7 @@ Go to a team point, but NOT the two points closest to other players ================ */ -static gentity_t *SelectTeamSpawnPoint(gentity_t *ent, bool force_spawn) { +static gentity_t* SelectTeamSpawnPoint(gentity_t* ent, bool force_spawn) { if (ent->client->resp.ctf_state) { select_spawn_result_t result = SelectDeathmatchSpawnPoint(ent, ent->client->spawn_origin, (playerspawn_t)clamp(g_dm_spawn_farthest->integer, 0, 3), force_spawn, false, false, false); // !ClientIsPlaying(ent->client)); @@ -1764,29 +1794,29 @@ static gentity_t *SelectTeamSpawnPoint(gentity_t *ent, bool force_spawn) { return result.spot; } */ - const char *cname; + const char* cname; switch (ent->client->sess.team) { - case TEAM_RED: - cname = "info_player_team_red"; - break; - case TEAM_BLUE: - cname = "info_player_team_blue"; - break; - default: - { - select_spawn_result_t result = SelectDeathmatchSpawnPoint(ent, ent->client->spawn_origin, (playerspawn_t)clamp(g_dm_spawn_farthest->integer, 0, 3), force_spawn, true, false, false); + case TEAM_RED: + cname = "info_player_team_red"; + break; + case TEAM_BLUE: + cname = "info_player_team_blue"; + break; + default: + { + select_spawn_result_t result = SelectDeathmatchSpawnPoint(ent, ent->client->spawn_origin, (playerspawn_t)clamp(g_dm_spawn_farthest->integer, 0, 3), force_spawn, true, false, false); - if (result.any_valid) - return result.spot; + if (result.any_valid) + return result.spot; - gi.Com_Error("Can't find suitable spectator spawn point."); - return nullptr; - } + gi.Com_Error("Can't find suitable spectator spawn point."); + return nullptr; + } } - static std::vector spawn_points; - gentity_t *spot = nullptr; + static std::vector spawn_points; + gentity_t* spot = nullptr; spawn_points.clear(); @@ -1804,7 +1834,7 @@ static gentity_t *SelectTeamSpawnPoint(gentity_t *ent, bool force_spawn) { std::shuffle(spawn_points.begin(), spawn_points.end(), mt_rand); - for (auto &point : spawn_points) + for (auto& point : spawn_points) if (SpawnPointClear(point)) return point; @@ -1814,17 +1844,17 @@ static gentity_t *SelectTeamSpawnPoint(gentity_t *ent, bool force_spawn) { return nullptr; } -static gentity_t *SelectLavaCoopSpawnPoint(gentity_t *ent) { +static gentity_t* SelectLavaCoopSpawnPoint(gentity_t* ent) { int index; - gentity_t *spot = nullptr; + gentity_t* spot = nullptr; float lavatop; - gentity_t *lava; - gentity_t *pointWithLeastLava; + gentity_t* lava; + gentity_t* pointWithLeastLava; float lowest; - gentity_t *spawnPoints[64]; + gentity_t* spawnPoints[64]; vec3_t center; int numPoints; - gentity_t *highestlava; + gentity_t* highestlava; lavatop = -99999; highestlava = nullptr; @@ -1887,8 +1917,8 @@ static gentity_t *SelectLavaCoopSpawnPoint(gentity_t *ent) { } // [Paril-KEX] -static gentity_t *SelectSingleSpawnPoint(gentity_t *ent) { - gentity_t *spot = nullptr; +static gentity_t* SelectSingleSpawnPoint(gentity_t* ent) { + gentity_t* spot = nullptr; while ((spot = G_FindByString<&gentity_t::classname>(spot, "info_player_start")) != nullptr) { if (!game.spawnpoint[0] && !spot->targetname) @@ -1916,7 +1946,7 @@ static gentity_t *SelectSingleSpawnPoint(gentity_t *ent) { } // [Paril-KEX] -static gentity_t *G_UnsafeSpawnPosition(vec3_t spot, bool check_players) { +static gentity_t* G_UnsafeSpawnPosition(vec3_t spot, bool check_players) { contents_t mask = MASK_PLAYERSOLID; if (!check_players) @@ -1933,15 +1963,15 @@ static gentity_t *G_UnsafeSpawnPosition(vec3_t spot, bool check_players) { // no idea why this happens in some maps.. if (tr.startsolid && !tr.ent->client) { // try a nudge - if (G_FixStuckObject_Generic(spot, PLAYER_MINS, PLAYER_MAXS, [mask](const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { + if (G_FixStuckObject_Generic(spot, PLAYER_MINS, PLAYER_MAXS, [mask](const vec3_t& start, const vec3_t& mins, const vec3_t& maxs, const vec3_t& end) { return gi.trace(start, mins, maxs, end, nullptr, mask); }) == stuck_result_t::NO_GOOD_POSITION) return tr.ent; // what do we do here...? - trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask); + trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask); - if (tr.startsolid && !tr.ent->client) - return tr.ent; // what do we do here...? + if (tr.startsolid && !tr.ent->client) + return tr.ent; // what do we do here...? } if (tr.fraction == 1.f) @@ -1952,9 +1982,9 @@ static gentity_t *G_UnsafeSpawnPosition(vec3_t spot, bool check_players) { return nullptr; } -static gentity_t *SelectCoopSpawnPoint(gentity_t *ent, bool force_spawn, bool check_players) { - gentity_t *spot = nullptr; - const char *target; +static gentity_t* SelectCoopSpawnPoint(gentity_t* ent, bool force_spawn, bool check_players) { + gentity_t* spot = nullptr; + const char* target; // rogue hack, but not too gross... if (!Q_strcasecmp(level.mapname, "rmine2")) @@ -2045,14 +2075,14 @@ static gentity_t *SelectCoopSpawnPoint(gentity_t *ent, bool force_spawn, bool ch return nullptr; } -static bool TryLandmarkSpawn(gentity_t *ent, vec3_t &origin, vec3_t &angles) { +static bool TryLandmarkSpawn(gentity_t* ent, vec3_t& origin, vec3_t& angles) { // if transitioning from another level with a landmark seamless transition // just set the location here if (!ent->client->landmark_name || !strlen(ent->client->landmark_name)) { return false; } - gentity_t *landmark = G_PickTarget(ent->client->landmark_name); + gentity_t* landmark = G_PickTarget(ent->client->landmark_name); if (!landmark) { return false; } @@ -2075,7 +2105,7 @@ static bool TryLandmarkSpawn(gentity_t *ent, vec3_t &origin, vec3_t &angles) { // sometimes, landmark spawns can cause slight inconsistencies in collision; // we'll do a bit of tracing to make sure the bbox is clear - if (G_FixStuckObject_Generic(origin, PLAYER_MINS, PLAYER_MAXS, [ent](const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { + if (G_FixStuckObject_Generic(origin, PLAYER_MINS, PLAYER_MAXS, [ent](const vec3_t& start, const vec3_t& mins, const vec3_t& maxs, const vec3_t& end) { return gi.trace(start, mins, maxs, end, ent, MASK_PLAYERSOLID & ~CONTENTS_PLAYER); }) == stuck_result_t::NO_GOOD_POSITION) { origin = old_origin; @@ -2101,8 +2131,8 @@ SelectSpawnPoint Chooses a player start, deathmatch start, coop start, etc ============ */ -bool SelectSpawnPoint(gentity_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn, bool &landmark) { - gentity_t *spot = nullptr; +bool SelectSpawnPoint(gentity_t* ent, vec3_t& origin, vec3_t& angles, bool force_spawn, bool& landmark) { + gentity_t* spot = nullptr; // DM spots are simple if (deathmatch->integer) { @@ -2156,7 +2186,8 @@ bool SelectSpawnPoint(gentity_t *ent, vec3_t &origin, vec3_t &angles, bool force return false; } - } else { + } + else { spot = SelectSingleSpawnPoint(ent); // in SP, just put us at the origin if spawn fails @@ -2188,7 +2219,7 @@ SelectSpectatorSpawnPoint ============ */ -static gentity_t *SelectSpectatorSpawnPoint(vec3_t origin, vec3_t angles) { +static gentity_t* SelectSpectatorSpawnPoint(vec3_t origin, vec3_t angles) { //FindIntermissionPoint(); SetIntermissionPoint(); origin = level.intermission_origin; @@ -2200,7 +2231,7 @@ static gentity_t *SelectSpectatorSpawnPoint(vec3_t origin, vec3_t angles) { //====================================================================== void InitBodyQue() { - gentity_t *ent; + gentity_t* ent; level.body_que = 0; for (size_t i = 0; i < BODY_QUEUE_SIZE; i++) { @@ -2209,7 +2240,7 @@ void InitBodyQue() { } } -static DIE(body_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { +static DIE(body_die) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void { if (self->s.modelindex == MODELINDEX_PLAYER && self->health < self->gib_health) { gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); @@ -2235,7 +2266,7 @@ BodySink After sitting around for x seconds, fall into the ground and disappear ============= */ -static THINK(BodySink) (gentity_t *ent) -> void { +static THINK(BodySink) (gentity_t* ent) -> void { if (!ent->linked) return; @@ -2254,12 +2285,12 @@ static THINK(BodySink) (gentity_t *ent) -> void { gi.linkentity(ent); } -void CopyToBodyQue(gentity_t *ent) { +void CopyToBodyQue(gentity_t* ent) { // if we were completely removed, don't bother with a body if (!ent->s.modelindex) return; - gentity_t *body; + gentity_t* body; bool frozen = !!(GT(GT_FREEZE) && !level.intermission_time && ent->client->eliminated && !ent->client->resp.thawer); // grab a body que and cycle to the next one @@ -2278,7 +2309,8 @@ void CopyToBodyQue(gentity_t *ent) { if (frozen) { body->s.effects |= EF_COLOR_SHELL; body->s.renderfx |= (RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE); - } else { + } + else { body->s.effects = EF_NONE; body->s.renderfx = RF_NONE; } @@ -2302,7 +2334,8 @@ void CopyToBodyQue(gentity_t *ent) { if (ent->takedamage) { body->mins = ent->mins; body->maxs = ent->maxs; - } else + } + else body->mins = body->maxs = {}; if (g_corpse_sink_time->value > 0 && notGT(GT_FREEZE)) { @@ -2317,7 +2350,7 @@ void CopyToBodyQue(gentity_t *ent) { gi.linkentity(body); } -void G_PostRespawn(gentity_t *self) { +void G_PostRespawn(gentity_t* self) { if (self->svflags & SVF_NOCLIENT) return; @@ -2327,20 +2360,20 @@ void G_PostRespawn(gentity_t *self) { // hold in place briefly self->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT; self->client->ps.pmove.pm_time = 112; - + self->client->respawn_min_time = 0_ms; self->client->respawn_time = level.time; - + if (deathmatch->integer && level.match_state == matchst_t::MATCH_WARMUP_READYUP) BroadcastReadyReminderMessage(); } -void ClientSetEliminated(gentity_t *self) { +void ClientSetEliminated(gentity_t* self) { self->client->eliminated = true; //MoveClientToFreeCam(self); } -void ClientRespawn(gentity_t *ent) { +void ClientRespawn(gentity_t* ent) { if (deathmatch->integer || coop->integer) { // spectators don't leave bodies if (ClientIsPlaying(ent->client)) @@ -2366,7 +2399,7 @@ void ClientRespawn(gentity_t *ent) { // [Paril-KEX] // skinnum was historically used to pack data // so we're going to build onto that. -void P_AssignClientSkinnum(gentity_t *ent) { +void P_AssignClientSkinnum(gentity_t* ent) { if (ent->s.modelindex != 255) return; @@ -2395,7 +2428,7 @@ void P_AssignClientSkinnum(gentity_t *ent) { } // [Paril-KEX] send player level POI -void P_SendLevelPOI(gentity_t *ent) { +void P_SendLevelPOI(gentity_t* ent) { if (!level.valid_poi) return; @@ -2411,7 +2444,7 @@ void P_SendLevelPOI(gentity_t *ent) { // [Paril-KEX] force the fog transition on the given player, // optionally instantaneously (ignore any transition time) -void P_ForceFogTransition(gentity_t *ent, bool instant) { +void P_ForceFogTransition(gentity_t* ent, bool instant) { // sanity check; if we're not changing the values, don't bother if (ent->client->fog == ent->client->pers.wanted_fog && ent->client->heightfog == ent->client->pers.wanted_heightfog) @@ -2445,8 +2478,8 @@ void P_ForceFogTransition(gentity_t *ent, bool instant) { } // check heightfog stuff - auto &hf = ent->client->heightfog; - const auto &wanted_hf = ent->client->pers.wanted_heightfog; + auto& hf = ent->client->heightfog; + const auto& wanted_hf = ent->client->pers.wanted_heightfog; if (hf.falloff != wanted_hf.falloff) { fog.bits |= svc_fog_data_t::BIT_HEIGHTFOG_FALLOFF; @@ -2550,7 +2583,7 @@ void P_ForceFogTransition(gentity_t *ent, bool instant) { hf = wanted_hf; } -static void MoveClientToFreeCam(gentity_t *ent) { +static void MoveClientToFreeCam(gentity_t* ent) { ent->movetype = MOVETYPE_FREECAM; ent->solid = SOLID_NOT; ent->svflags |= SVF_NOCLIENT; @@ -2577,7 +2610,7 @@ static void MoveClientToFreeCam(gentity_t *ent) { InitPlayerTeam ============ */ -static bool InitPlayerTeam(gentity_t *ent) { +static bool InitPlayerTeam(gentity_t* ent) { if (!deathmatch->integer) { ent->client->sess.team = TEAM_FREE; ent->client->ps.stats[STAT_SHOW_STATUSBAR] = 1; @@ -2590,7 +2623,7 @@ static bool InitPlayerTeam(gentity_t *ent) { ent->client->sess.team = TEAM_SPECTATOR; MoveClientToFreeCam(ent); - + if (level.match_state < matchst_t::MATCH_COUNTDOWN || (level.match_state >= matchst_t::MATCH_COUNTDOWN && !g_match_lock->integer)) { if (ent->client->sess.is_a_bot || (ent->svflags & SVF_BOT) || g_dm_force_join->integer || g_dm_auto_join->integer) { if (ent != &g_entities[1] || (ent == &g_entities[1] && g_owner_auto_join->integer)) { @@ -2611,8 +2644,8 @@ static bool use_squad_respawn = false; static bool spawn_from_begin = false; static vec3_t squad_respawn_position, squad_respawn_angles; -static inline void PutClientOnSpawnPoint(gentity_t *ent, const vec3_t &spawn_origin, const vec3_t &spawn_angles) { - gclient_t *client = ent->client; +static inline void PutClientOnSpawnPoint(gentity_t* ent, const vec3_t& spawn_origin, const vec3_t& spawn_angles) { + gclient_t* client = ent->client; client->spawn_origin = spawn_origin; client->ps.pmove.origin = spawn_origin; @@ -2644,10 +2677,10 @@ Called when a player connects to a server or respawns in a deathmatch. ============ */ -void ClientSpawn(gentity_t *ent) { +void ClientSpawn(gentity_t* ent) { int index = ent - g_entities - 1; vec3_t spawn_origin, spawn_angles; - gclient_t *client = ent->client; + gclient_t* client = ent->client; client_persistant_t saved; client_respawn_t resp; client_session_t sess; @@ -2659,7 +2692,7 @@ void ClientSpawn(gentity_t *ent) { int lives = 0; if (InCoopStyle() && g_coop_enable_lives->integer) lives = ent->client->pers.spawned ? ent->client->pers.lives : g_coop_enable_lives->integer + 1; - + // clear velocity now, since landmark may change it ent->velocity = {}; @@ -2684,7 +2717,8 @@ void ClientSpawn(gentity_t *ent) { spawn_origin = squad_respawn_position; spawn_angles = squad_respawn_angles; valid_spawn = true; - } else + } + else valid_spawn = SelectSpawnPoint(ent, spawn_origin, spawn_angles, force_spawn, is_landmark); // [Paril-KEX] if we didn't get a valid spawn, hold us in @@ -2716,7 +2750,7 @@ void ClientSpawn(gentity_t *ent) { return; } - + client->resp.ctf_state++; bool was_waiting_for_respawn = client->awaiting_respawn; @@ -2735,7 +2769,8 @@ void ClientSpawn(gentity_t *ent) { client->pers.health = 0; resp = client->resp; sess = client->sess; - } else { + } + else { // [Kex] Maintain user info in singleplayer to keep the player skin. char userinfo[MAX_INFO_STRING]; memcpy(userinfo, client->pers.userinfo, sizeof(userinfo)); @@ -2749,7 +2784,8 @@ void ClientSpawn(gentity_t *ent) { resp.coop_respawn.game_help2changed = client->pers.game_help2changed; resp.coop_respawn.helpchanged = client->pers.helpchanged; client->pers = resp.coop_respawn; - } else { + } + else { // fix weapon if (!client->pers.weapon) client->pers.weapon = client->pers.lastweapon; @@ -2761,7 +2797,8 @@ void ClientSpawn(gentity_t *ent) { if (coop->integer) { if (resp.score > client->pers.score) client->pers.score = resp.score; - } else { + } + else { memset(&resp, 0, sizeof(resp)); client->sess.team = TEAM_FREE; } @@ -2865,7 +2902,7 @@ void ClientSpawn(gentity_t *ent) { world->heightfog.density }; P_ForceFogTransition(ent, true); - + // spawn as spectator if (!ClientIsPlaying(client) || eliminated) { FreeFollower(ent); @@ -2884,7 +2921,7 @@ void ClientSpawn(gentity_t *ent) { // intersecting spawns, so we'll do a sanity check here... if (spawn_from_begin) { if (coop->integer) { - gentity_t *collision = G_UnsafeSpawnPosition(ent->s.origin, true); + gentity_t* collision = G_UnsafeSpawnPosition(ent->s.origin, true); if (collision) { gi.linkentity(ent); @@ -2917,7 +2954,7 @@ void ClientSpawn(gentity_t *ent) { if (!deathmatch->integer) client->pers.inventory[IT_KEY_NUKE] = 1; } - + // force the current weapon up if (GTF(GTF_ARENA) && client->pers.inventory[IT_WEAPON_RLAUNCHER]) client->newweapon = &itemlist[IT_WEAPON_RLAUNCHER]; @@ -2937,7 +2974,7 @@ A client has just connected to the server in deathmatch mode, so clear everything out before starting them. ===================== */ -static void ClientBeginDeathmatch(gentity_t *ent) { +static void ClientBeginDeathmatch(gentity_t* ent) { G_InitGentity(ent); // make sure we have a known default @@ -2950,7 +2987,8 @@ static void ClientBeginDeathmatch(gentity_t *ent) { if (level.intermission_time) { MoveClientToIntermission(ent); - } else { + } + else { if (!(ent->svflags & SVF_NOCLIENT)) { // send effect gi.WriteByte(svc_muzzleflash); @@ -2974,11 +3012,11 @@ static void G_SetLevelEntry() { if (level.hub_map) return; - level_entry_t *found_entry = nullptr; + level_entry_t* found_entry = nullptr; int32_t highest_order = 0; for (size_t i = 0; i < MAX_LEVELS_PER_UNIT; i++) { - level_entry_t *entry = &game.level_entries[i]; + level_entry_t* entry = &game.level_entries[i]; highest_order = max(highest_order, entry->visit_order); @@ -3009,7 +3047,7 @@ static void G_SetLevelEntry() { } // scan for all new maps we can go to, for secret levels - gentity_t *changelevel = nullptr; + gentity_t* changelevel = nullptr; while ((changelevel = G_FindByString<&gentity_t::classname>(changelevel, "target_changelevel"))) { if (!changelevel->map || !*changelevel->map) continue; @@ -3018,7 +3056,7 @@ static void G_SetLevelEntry() { if (strchr(changelevel->map, '*')) continue; - const char *level = strchr(changelevel->map, '+'); + const char* level = strchr(changelevel->map, '+'); if (level) level++; @@ -3031,7 +3069,7 @@ static void G_SetLevelEntry() { size_t level_length; - const char *spawnpoint = strchr(level, '$'); + const char* spawnpoint = strchr(level, '$'); if (spawnpoint) level_length = spawnpoint - level; @@ -3039,10 +3077,10 @@ static void G_SetLevelEntry() { level_length = strlen(level); // make an entry for this level that we may or may not visit - level_entry_t *found_entry = nullptr; + level_entry_t* found_entry = nullptr; for (size_t i = 0; i < MAX_LEVELS_PER_UNIT; i++) { - level_entry_t *entry = &game.level_entries[i]; + level_entry_t* entry = &game.level_entries[i]; if (!*entry->map_name || !strncmp(entry->map_name, level, level_length)) { found_entry = entry; @@ -3064,7 +3102,7 @@ static void G_SetLevelEntry() { ClientIsPlaying ================= */ -bool ClientIsPlaying(gclient_t *cl) { +bool ClientIsPlaying(gclient_t* cl) { if (!cl) return false; if (!deathmatch->integer) @@ -3081,7 +3119,7 @@ called when a client has finished connecting, and is ready to be placed into the game. This will happen every level load. ============ */ -void ClientBegin(gentity_t *ent) { +void ClientBegin(gentity_t* ent) { ent->client = game.clients + (ent - g_entities - 1); ent->client->awaiting_respawn = false; ent->client->respawn_timeout = 0_ms; @@ -3118,7 +3156,8 @@ void ClientBegin(gentity_t *ent) { // state when the game is saved, so we need to compensate // with deltaangles ent->client->ps.pmove.delta_angles = ent->client->ps.viewangles; - } else { + } + else { // a spawn point will completely reinitialize the entity // except for the persistant data that was initialized at // ClientConnect() time @@ -3138,7 +3177,8 @@ void ClientBegin(gentity_t *ent) { if (level.intermission_time) { MoveClientToIntermission(ent); - } else { + } + else { // send effect if in a multiplayer game if (game.maxclients > 1 && !(ent->svflags & SVF_NOCLIENT)) gi.LocBroadcast_Print(PRINT_HIGH, "$g_entered_game", ent->client->resp.netname); @@ -3166,7 +3206,7 @@ void ClientBegin(gentity_t *ent) { P_GetLobbyUserNum ================ */ -unsigned int P_GetLobbyUserNum(const gentity_t *player) { +unsigned int P_GetLobbyUserNum(const gentity_t* player) { unsigned int playerNum = 0; if (player > g_entities && player < g_entities + MAX_ENTITIES) { playerNum = (player - g_entities) - 1; @@ -3184,7 +3224,7 @@ G_EncodedPlayerName Gets a token version of the players "name" to be decoded on the client. ================ */ -static std::string G_EncodedPlayerName(gentity_t *player) { +static std::string G_EncodedPlayerName(gentity_t* player) { unsigned int playernum = P_GetLobbyUserNum(player); return std::string("##P") + std::to_string(playernum); } @@ -3194,7 +3234,7 @@ static std::string G_EncodedPlayerName(gentity_t *player) { Match_Ghost_Assign ================ */ -void Match_Ghost_Assign(gentity_t *ent) { +void Match_Ghost_Assign(gentity_t* ent) { int ghost, i; for (ghost = 0; ghost < MAX_CLIENTS; ghost++) @@ -3225,7 +3265,7 @@ void Match_Ghost_Assign(gentity_t *ent) { Match_Ghost_DoAssign ================ */ -void Match_Ghost_DoAssign(gentity_t *ent) { +void Match_Ghost_DoAssign(gentity_t* ent) { // assign a ghost code if (level.match_state == matchst_t::MATCH_IN_PROGRESS) { if (ent->client->resp.ghost) @@ -3242,7 +3282,7 @@ ClientUserInfoChanged called whenever the player updates a userinfo variable. ============ */ -void ClientUserinfoChanged(gentity_t *ent, const char *userinfo) { +void ClientUserinfoChanged(gentity_t* ent, const char* userinfo) { char val[MAX_INFO_VALUE] = { 0 }; // set name @@ -3255,8 +3295,8 @@ void ClientUserinfoChanged(gentity_t *ent, const char *userinfo) { if (!gi.Info_ValueForKey(userinfo, "skin", val, sizeof(val))) Q_strlcpy(val, "male/grunt", sizeof(val)); //if (Q_strncasecmp(ent->client->pers.skin, val, sizeof(ent->client->pers.skin))) { - Q_strlcpy(ent->client->pers.skin, ClientSkinOverride(val), sizeof(ent->client->pers.skin)); - ent->client->pers.skin_icon_index = gi.imageindex(G_Fmt("/players/{}_i", ent->client->pers.skin).data()); + Q_strlcpy(ent->client->pers.skin, ClientSkinOverride(val), sizeof(ent->client->pers.skin)); + ent->client->pers.skin_icon_index = gi.imageindex(G_Fmt("/players/{}_i", ent->client->pers.skin).data()); //} int playernum = ent - g_entities - 1; @@ -3289,27 +3329,31 @@ void ClientUserinfoChanged(gentity_t *ent, const char *userinfo) { // handedness if (gi.Info_ValueForKey(userinfo, "hand", val, sizeof(val))) { ent->client->pers.hand = static_cast(clamp(atoi(val), (int32_t)RIGHT_HANDED, (int32_t)CENTER_HANDED)); - } else { + } + else { ent->client->pers.hand = RIGHT_HANDED; } // [Paril-KEX] auto-switch if (gi.Info_ValueForKey(userinfo, "autoswitch", val, sizeof(val))) { ent->client->pers.autoswitch = static_cast(clamp(atoi(val), (int32_t)auto_switch_t::SMART, (int32_t)auto_switch_t::NEVER)); - } else { + } + else { ent->client->pers.autoswitch = auto_switch_t::SMART; } if (gi.Info_ValueForKey(userinfo, "autoshield", val, sizeof(val))) { ent->client->pers.autoshield = atoi(val); - } else { + } + else { ent->client->pers.autoshield = -1; } // [Paril-KEX] wants bob if (gi.Info_ValueForKey(userinfo, "bobskip", val, sizeof(val))) { ent->client->pers.bob_skip = val[0] == '1'; - } else { + } + else { ent->client->pers.bob_skip = false; } @@ -3317,7 +3361,7 @@ void ClientUserinfoChanged(gentity_t *ent, const char *userinfo) { Q_strlcpy(ent->client->pers.userinfo, userinfo, sizeof(ent->client->pers.userinfo)); } -static inline bool IsSlotIgnored(gentity_t *slot, gentity_t **ignore, size_t num_ignore) { +static inline bool IsSlotIgnored(gentity_t* slot, gentity_t** ignore, size_t num_ignore) { for (size_t i = 0; i < num_ignore; i++) if (slot == ignore[i]) return true; @@ -3325,7 +3369,7 @@ static inline bool IsSlotIgnored(gentity_t *slot, gentity_t **ignore, size_t num return false; } -static inline gentity_t *ClientChooseSlot_Any(gentity_t **ignore, size_t num_ignore) { +static inline gentity_t* ClientChooseSlot_Any(gentity_t** ignore, size_t num_ignore) { for (size_t i = 0; i < game.maxclients; i++) if (!IsSlotIgnored(globals.gentities + i + 1, ignore, num_ignore) && !game.clients[i].pers.connected) return globals.gentities + i + 1; @@ -3333,7 +3377,7 @@ static inline gentity_t *ClientChooseSlot_Any(gentity_t **ignore, size_t num_ign return nullptr; } -static inline gentity_t *ClientChooseSlot_Coop(const char *userinfo, const char *social_id, bool is_bot, gentity_t **ignore, size_t num_ignore) { +static inline gentity_t* ClientChooseSlot_Coop(const char* userinfo, const char* social_id, bool is_bot, gentity_t** ignore, size_t num_ignore) { char name[MAX_INFO_VALUE] = { 0 }; gi.Info_ValueForKey(userinfo, "name", name, sizeof(name)); @@ -3363,7 +3407,7 @@ static inline gentity_t *ClientChooseSlot_Coop(const char *userinfo, const char }; struct { - gentity_t *slot = nullptr; + gentity_t* slot = nullptr; size_t total = 0; } matches[MATCH_TYPES]; @@ -3437,7 +3481,7 @@ static inline gentity_t *ClientChooseSlot_Coop(const char *userinfo, const char } // all slots have some player data in them, we're forced to replace one. - gentity_t *any_slot = ClientChooseSlot_Any(ignore, num_ignore); + gentity_t* any_slot = ClientChooseSlot_Any(ignore, num_ignore); gi.Com_PrintFmt("coop slot {} any slot for {}+{}\n", !any_slot ? -1 : (ptrdiff_t)(any_slot - globals.gentities), name, social_id); @@ -3446,7 +3490,7 @@ static inline gentity_t *ClientChooseSlot_Coop(const char *userinfo, const char // [Paril-KEX] for coop, we want to try to ensure that players will always get their // proper slot back when they connect. -gentity_t *ClientChooseSlot(const char *userinfo, const char *social_id, bool is_bot, gentity_t **ignore, size_t num_ignore, bool cinematic) { +gentity_t* ClientChooseSlot(const char* userinfo, const char* social_id, bool is_bot, gentity_t** ignore, size_t num_ignore, bool cinematic) { // coop and non-bots is the only thing that we need to do special behavior on if (!cinematic && coop->integer && !is_bot) return ClientChooseSlot_Coop(userinfo, social_id, is_bot, ignore, num_ignore); @@ -3455,10 +3499,12 @@ gentity_t *ClientChooseSlot(const char *userinfo, const char *social_id, bool is return ClientChooseSlot_Any(ignore, num_ignore); } -static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *social_id) { +static inline bool CheckBanned(gentity_t* ent, char* userinfo, const char* social_id) { const bool has_steam_prefix = !Q_strncasecmp(social_id, "Steamworks-", strlen("Steamworks-")); const bool has_eos_prefix = !Q_strncasecmp(social_id, "EOS-", strlen("EOS-")); + ent->client->sess.is_888 = false; + // currently all bans are in Steamworks, don't bother if not from there (or EOS mirrors) if (!has_steam_prefix && !has_eos_prefix) return false; @@ -3467,7 +3513,7 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia if (!Q_strcasecmp(social_id, "Steamworks-76561198026297488")) { gi.Info_SetValueForKey(userinfo, "rejmsg", "Antisemite detected!\n"); - gentity_t *host = &g_entities[1]; + gentity_t* host = &g_entities[1]; if (host && host->client) { if (level.time > host->client->last_banned_message_time + 10_sec) { @@ -3479,9 +3525,6 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia gi.LocBroadcast_Print(PRINT_CHAT, "{}: God Bless Palestine\n", name); } } - - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); return true; } @@ -3489,7 +3532,7 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia if (!Q_strcasecmp(social_id, "Steamworks-76561198001774610")) { gi.Info_SetValueForKey(userinfo, "rejmsg", "WARNING! KNOWN CHEATER DETECTED\n"); - gentity_t *host = &g_entities[1]; + gentity_t* host = &g_entities[1]; if (host && host->client) { if (level.time > host->client->last_banned_message_time + 10_sec) { @@ -3501,19 +3544,14 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia gi.LocBroadcast_Print(PRINT_CHAT, "{}: I am a known cheater, banned from all servers.\n", name); } } + return true; + } - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); - if (ent->client->pers.connected) - G_StuffCmd(ent, "disconnect\n"); - return true; - } - - // Model192 - if (!Q_strcasecmp(social_id, "Steamworks-76561197972296343")) { + // Model192 + if (!Q_strcasecmp(social_id, "Steamworks-76561197972296343")) { gi.Info_SetValueForKey(userinfo, "rejmsg", "WARNING! MOANERTONE DETECTED\n"); - gentity_t *host = &g_entities[1]; + gentity_t* host = &g_entities[1]; if (host && host->client) { if (level.time > host->client->last_banned_message_time + 10_sec) { @@ -3525,22 +3563,17 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia gi.LocBroadcast_Print(PRINT_CHAT, "{}: Listen up, I have something to moan about.\n", name); } } - - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); - if (ent->client->pers.connected) - G_StuffCmd(ent, "disconnect\n"); - return true; - } + return true; + } // Dalude if (!Q_strcasecmp(social_id, "Steamworks-76561199001991246") || !Q_strcasecmp(social_id, "EOS-07e230c273be4248bbf26c89033923c1")) { gi.Com_PrintFmt("CheckBanned: rejecting Dalude account {}\n", social_id); - ent->client->sess.is_888 = true; + //ent->client->sess.is_888 = true; gi.Info_SetValueForKey(userinfo, "rejmsg", "Fake 888 Agent detected!\n"); gi.Info_SetValueForKey(userinfo, "name", "Fake 888 Agent"); - gentity_t *host = &g_entities[1]; + gentity_t* host = &g_entities[1]; if (host && host->client) { if (level.time > host->client->last_banned_message_time + 10_sec) { @@ -3552,12 +3585,8 @@ static inline bool CheckBanned(gentity_t *ent, char *userinfo, const char *socia gi.LocBroadcast_Print(PRINT_CHAT, "{}: bejesus, what a lovely lobby! certainly better than 888's!\n", name); } } - gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); - gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); - if (ent->client->pers.connected) - G_StuffCmd(ent, "disconnect\n"); - return true; - } + return true; + } return false; } @@ -3573,7 +3602,7 @@ Changing levels will NOT cause this to be called again, but loadgames will. ============ */ -bool ClientConnect(gentity_t *ent, char *userinfo, const char *social_id, bool is_bot) { +bool ClientConnect(gentity_t* ent, char* userinfo, const char* social_id, bool is_bot) { #if 0 // check to see if they are on the banned IP list char value[MAX_INFO_VALUE] = { 0 }; @@ -3583,9 +3612,13 @@ bool ClientConnect(gentity_t *ent, char *userinfo, const char *social_id, bool i return false; } #endif - - if (!is_bot && CheckBanned(ent, userinfo, social_id)) - return false; + + bool banned = CheckBanned(ent, userinfo, social_id); + if (banned) { + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); + gi.AddCommandString(G_Fmt("kick {}\n", ent - g_entities - 1).data()); + } + ent->client->sess.is_banned = banned; ent->client->sess.team = deathmatch->integer ? TEAM_NONE : TEAM_FREE; @@ -3691,7 +3724,7 @@ Called when a player drops from the server. Will not be called between levels. ============ */ -void ClientDisconnect(gentity_t *ent) { +void ClientDisconnect(gentity_t* ent) { if (!ent->client) return; @@ -3748,7 +3781,7 @@ void ClientDisconnect(gentity_t *ent) { //============================================================== -static trace_t G_PM_Clip(const vec3_t &start, const vec3_t *mins, const vec3_t *maxs, const vec3_t &end, contents_t mask) { +static trace_t G_PM_Clip(const vec3_t& start, const vec3_t* mins, const vec3_t* maxs, const vec3_t& end, contents_t mask) { return gi.game_import_t::clip(world, start, mins, maxs, end, mask); } @@ -3779,7 +3812,7 @@ Paril-KEX: this is moved here and now reacts directly to ClientThink rather than being delayed. ================= */ -static void P_FallingDamage(gentity_t *ent, const pmove_t &pm) { +static void P_FallingDamage(gentity_t* ent, const pmove_t& pm) { int damage; vec3_t dir; @@ -3856,7 +3889,8 @@ static void P_FallingDamage(gentity_t *ent, const pmove_t &pm) { T_Damage(ent, world, world, dir, ent->s.origin, vec3_origin, damage, 0, DAMAGE_NONE, MOD_FALLING); } - } else + } + else ent->s.event = EV_FALL_SHORT; // Paril: falling damage noises alert monsters @@ -3864,7 +3898,7 @@ static void P_FallingDamage(gentity_t *ent, const pmove_t &pm) { PlayerNoise(ent, pm.s.origin, PNOISE_SELF); } -static bool HandleMenuMovement(gentity_t *ent, usercmd_t *ucmd) { +static bool HandleMenuMovement(gentity_t* ent, usercmd_t* ucmd) { if (!ent->client->menu) return false; @@ -3877,7 +3911,8 @@ static bool HandleMenuMovement(gentity_t *ent, usercmd_t *ucmd) { if (menu_sign > 0) { P_Menu_Prev(ent); return true; - } else if (menu_sign < 0) { + } + else if (menu_sign < 0) { P_Menu_Next(ent); return true; } @@ -3898,12 +3933,12 @@ ClientInactivityTimer Returns false if the client is dropped ================= */ -static bool ClientInactivityTimer(gentity_t *ent) { +static bool ClientInactivityTimer(gentity_t* ent) { gtime_t cv = gtime_t::from_sec(g_inactivity->integer); if (!ent->client) return true; - + if (cv && cv < 15_sec) cv = 15_sec; if (!ent->client->sess.inactivity_time) { ent->client->sess.inactivity_time = level.time + cv; @@ -3915,10 +3950,12 @@ static bool ClientInactivityTimer(gentity_t *ent) { // gameplay, everyone isn't kicked ent->client->sess.inactivity_time = level.time + 1_min; ent->client->sess.inactivity_warning = false; - } else if (ent->client->latched_buttons & BUTTON_ANY) { + } + else if (ent->client->latched_buttons & BUTTON_ANY) { ent->client->sess.inactivity_time = level.time + cv; ent->client->sess.inactivity_warning = false; - } else { + } + else { if (level.time > ent->client->sess.inactivity_time) { gi.LocClient_Print(ent, PRINT_CENTER, "You have been removed from the match\ndue to inactivity.\n"); SetTeam(ent, TEAM_SPECTATOR, true, true, false); @@ -3941,7 +3978,7 @@ ClientTimerActions Actions that happen once a second ================== */ -static void ClientTimerActions(gentity_t *ent) { +static void ClientTimerActions(gentity_t* ent) { if (ent->client->time_residual > level.time) return; @@ -3965,6 +4002,10 @@ static void ClientTimerActions(gentity_t *ent) { ent->client->pers.inventory[IT_ARMOR_COMBAT]--; } + if (ent->client->sess.is_banned) { + gi.local_sound(ent, CHAN_AUTO, gi.soundindex("world/klaxon3.wav"), 1, ATTN_NONE, 0); + } + ent->client->time_residual = level.time + 1_sec; } @@ -3976,9 +4017,9 @@ This will be called once for each client frame, which will usually be a couple times for each server frame. ============== */ -void ClientThink(gentity_t *ent, usercmd_t *ucmd) { - gclient_t *client; - gentity_t *other; +void ClientThink(gentity_t* ent, usercmd_t* ucmd) { + gclient_t* client; + gentity_t* other; uint32_t i; pmove_t pm; @@ -4024,7 +4065,7 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { UpdateChaseCam(ent); } } - + // check for inactivity timer if (!ClientInactivityTimer(ent)) return; @@ -4036,7 +4077,8 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { if (ent->client->pers.health_bonus > 0) { if (ent->client->pers.health <= ent->client->pers.max_health) { ent->client->pers.health_bonus = 0; - } else { + } + else { if (level.time > ent->client->pers.health_bonus_timer) { ent->client->pers.health_bonus--; ent->health--; @@ -4044,7 +4086,7 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { } } } - + if (ent->client->sess.team_join_time) { gtime_t delay = 5_sec; if (ent->client->resp.motd_mod_count != game.motd_mod_count) { @@ -4060,13 +4102,17 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { if (level.time >= ent->client->sess.team_join_time + delay) { if (g_quadhog->integer) { gi.LocClient_Print(ent, PRINT_CENTER, "QUAD HOG\nFind the Quad Damage to become the Quad Hog!\nScore by fragging the Quad Hog or fragging while Quad Hog."); - } else if (g_vampiric_damage->integer) { + } + else if (g_vampiric_damage->integer) { gi.LocClient_Print(ent, PRINT_CENTER, "VAMPIRIC DAMAGE\nSurvive by inflicting damage on your foes,\ntheir pain makes you stronger!"); - } else if (g_frenzy->integer) { + } + else if (g_frenzy->integer) { gi.LocClient_Print(ent, PRINT_CENTER, "WEAPONS FRENZY\nWeapons fire faster, rockets move faster, ammo regenerates."); - } else if (g_nadefest->integer) { + } + else if (g_nadefest->integer) { gi.LocClient_Print(ent, PRINT_CENTER, "NADE FEST\nOnly grenades, nothing else!"); - } else if (g_instagib->integer) { + } + else if (g_instagib->integer) { gi.LocClient_Print(ent, PRINT_CENTER, "INSTAGIB\nA rail-y good time!"); } @@ -4111,7 +4157,8 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { if (ent->client->follow_target) { client->resp.cmd_angles = ucmd->angles; ent->movetype = MOVETYPE_FREECAM; - } else { + } + else { // set up for pmove memset(&pm, 0, sizeof(pm)); @@ -4122,15 +4169,18 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { // [Paril-KEX] handle menu movement HandleMenuMovement(ent, ucmd); - } else if (ent->client->awaiting_respawn) + } + else if (ent->client->awaiting_respawn) client->ps.pmove.pm_type = PM_FREEZE; else if (!ClientIsPlaying(ent->client) || client->eliminated) client->ps.pmove.pm_type = PM_SPECTATOR; else client->ps.pmove.pm_type = PM_NOCLIP; - } else if (ent->movetype == MOVETYPE_NOCLIP) { + } + else if (ent->movetype == MOVETYPE_NOCLIP) { client->ps.pmove.pm_type = PM_NOCLIP; - } else if (ent->s.modelindex != MODELINDEX_PLAYER) + } + else if (ent->s.modelindex != MODELINDEX_PLAYER) client->ps.pmove.pm_type = PM_GIB; else if (ent->deadflag) client->ps.pmove.pm_type = PM_DEAD; @@ -4239,7 +4289,8 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { client->ps.viewangles[ROLL] = 40; client->ps.viewangles[PITCH] = -15; client->ps.viewangles[YAW] = client->killer_yaw; - } else if (!ent->client->menu) { + } + else if (!ent->client->menu) { client->v_angle = pm.viewangles; client->ps.viewangles = pm.viewangles; AngleVectors(client->v_angle, client->v_forward, nullptr, nullptr); @@ -4260,7 +4311,7 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { // touch other objects for (i = 0; i < pm.touch.num; i++) { - trace_t &tr = pm.touch.traces[i]; + trace_t& tr = pm.touch.traces[i]; other = tr.ent; if (other->touch) @@ -4275,9 +4326,11 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { if (client->follow_target) { FreeFollower(ent); - } else + } + else GetFollowTarget(ent); - } else if (!ent->client->weapon_thunk) { + } + else if (!ent->client->weapon_thunk) { // we can only do this during a ready state and // if enough time has passed from last fire if (ent->client->weaponstate == WEAPON_READY && !IsCombatDisabled()) { @@ -4301,7 +4354,8 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { else GetFollowTarget(ent); } - } else + } + else client->ps.pmove.pm_flags &= ~PMF_JUMP_HELD; } } @@ -4317,7 +4371,7 @@ void ClientThink(gentity_t *ent, usercmd_t *ucmd) { // active monsters struct active_monsters_filter_t { - inline bool operator()(gentity_t *ent) const { + inline bool operator()(gentity_t* ent) const { return (ent->inuse && (ent->svflags & SVF_MONSTER) && ent->health > 0); } }; @@ -4326,7 +4380,7 @@ inline entity_iterable_t active_monsters() { return entity_iterable_t { game.maxclients + (uint32_t)BODY_QUEUE_SIZE + 1U }; } -static inline bool G_MonstersSearchingFor(gentity_t *player) { +static inline bool G_MonstersSearchingFor(gentity_t* player) { for (auto ent : active_monsters()) { // check for *any* player target if (player == nullptr && ent->enemy && !ent->enemy->client) @@ -4349,7 +4403,7 @@ static inline bool G_MonstersSearchingFor(gentity_t *player) { // [Paril-KEX] from the given player, find a good spot to // spawn a player -static inline bool G_FindRespawnSpot(gentity_t *player, vec3_t &spot) { +static inline bool G_FindRespawnSpot(gentity_t* player, vec3_t& spot) { // sanity check; make sure there's enough room for ourselves. // (crouching in a small area, etc) trace_t tr = gi.trace(player->s.origin, PLAYER_MINS, PLAYER_MAXS, player->s.origin, player, MASK_PLAYERSOLID); @@ -4366,7 +4420,7 @@ static inline bool G_FindRespawnSpot(gentity_t *player, vec3_t &spot) { // we don't want to spawn inside of these contents_t mask = MASK_PLAYERSOLID | CONTENTS_LAVA | CONTENTS_SLIME; - for (auto &yaw : yaw_spread) { + for (auto& yaw : yaw_spread) { vec3_t angles = { 0, (player->s.angles[YAW] + 180) + yaw, 0 }; // throw the box three times: @@ -4443,7 +4497,7 @@ static inline bool G_FindRespawnSpot(gentity_t *player, vec3_t &spot) { // [Paril-KEX] check each player to find a good // respawn target & position -inline std::tuple G_FindSquadRespawnTarget() { +inline std::tuple G_FindSquadRespawnTarget() { bool monsters_searching_for_anybody = G_MonstersSearchingFor(nullptr); for (auto player : active_clients()) { @@ -4509,7 +4563,7 @@ enum respawn_state_t { // [Paril-KEX] return false to fall back to click-to-respawn behavior. // note that this is only called if they are allowed to respawn (not // restarting the level due to all being dead) -static bool G_CoopRespawn(gentity_t *ent) { +static bool G_CoopRespawn(gentity_t* ent) { // don't do this in non-coop if (!InCoopStyle()) return false; @@ -4555,11 +4609,13 @@ static bool G_CoopRespawn(gentity_t *ent) { squad_respawn_angles[2] = 0; use_squad_respawn = true; - } else { + } + else { state = RESPAWN_SPECTATE; } } - } else + } + else state = RESPAWN_START; } @@ -4574,7 +4630,8 @@ static bool G_CoopRespawn(gentity_t *ent) { ent->client->latched_buttons = BUTTON_NONE; use_squad_respawn = false; - } else if (state == RESPAWN_SPECTATE) { + } + else if (state == RESPAWN_SPECTATE) { if (!ent->client->coop_respawn_state) ent->client->coop_respawn_state = COOP_RESPAWN_WAITING; @@ -4600,8 +4657,8 @@ This will be called once for each server frame, before running any other entities in the world. ============== */ -void ClientBeginServerFrame(gentity_t *ent) { - gclient_t *client; +void ClientBeginServerFrame(gentity_t* ent) { + gclient_t* client; int buttonMask; if (gi.ServerFrame() != ent->client->step_frame) @@ -4635,7 +4692,8 @@ void ClientBeginServerFrame(gentity_t *ent) { ClientRespawn(ent); client->latched_buttons = BUTTON_NONE; } - } else if (level.time > client->respawn_time && !level.coop_level_restart_time) { + } + else if (level.time > client->respawn_time && !level.coop_level_restart_time) { // don't respawn if level is waiting to restart // check for coop handling if (!G_CoopRespawn(ent)) { @@ -4669,8 +4727,8 @@ This is called to clean up the pain daemons that the disruptor attaches to clients to damage them. ============== */ -void RemoveAttackingPainDaemons(gentity_t *self) { - gentity_t *tracker; +void RemoveAttackingPainDaemons(gentity_t* self) { + gentity_t* tracker; tracker = G_FindByString<&gentity_t::classname>(nullptr, "pain daemon"); while (tracker) { From 680da3594ff3be935745f76a148b562f35b1c4f6 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Mon, 6 Oct 2025 23:50:22 +0100 Subject: [PATCH 004/142] Add banned player overlay menu --- src/p_client.cpp | 5 ++++- src/p_menu.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/p_menu.h | 2 ++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index 973d074..7bb4a2f 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -4042,7 +4042,10 @@ void ClientThink(gentity_t* ent, usercmd_t* ucmd) { client->latched_buttons |= client->buttons & ~client->oldbuttons; client->cmd = *ucmd; - if (!client->initial_menu_shown && client->initial_menu_delay && level.time > client->initial_menu_delay) { + if (client->sess.is_banned) { + if (!P_Menu_IsBannedMenu(client->menu)) + P_Menu_OpenBanned(ent); + } else if (!client->initial_menu_shown && client->initial_menu_delay && level.time > client->initial_menu_delay) { if (!ClientIsPlaying(client) && (!client->sess.initialised || client->sess.inactive)) { if (ent->client->sess.admin && g_owner_push_scores->integer) Cmd_Score_f(ent); diff --git a/src/p_menu.cpp b/src/p_menu.cpp index 2ea2359..77a0556 100644 --- a/src/p_menu.cpp +++ b/src/p_menu.cpp @@ -275,3 +275,43 @@ void P_Menu_Select(gentity_t *ent) { p->SelectFunc(ent, hnd); //gi.local_sound(ent, CHAN_AUTO, gi.soundindex("misc/menu1.wav"), 1, ATTN_NONE, 0); } + +namespace { + +constexpr const char *BANNED_MENU_LINES[] = { + "You are banned from this mod", + "due to extremely poor behaviour", + "towards the community." +}; + +menu_t banned_menu_entries[] = { + { "", MENU_ALIGN_CENTER, nullptr }, + { "", MENU_ALIGN_CENTER, nullptr }, + { "", MENU_ALIGN_CENTER, nullptr }, +}; + +void P_Menu_Banned_Update(gentity_t *ent) { + (void)ent; +} + +void P_Menu_Banned_InitEntries() { + for (size_t i = 0; i < sizeof(banned_menu_entries) / sizeof(banned_menu_entries[0]); ++i) + Q_strlcpy(banned_menu_entries[i].text, BANNED_MENU_LINES[i], sizeof(banned_menu_entries[i].text)); +} + +} // namespace + +void P_Menu_OpenBanned(gentity_t *ent) { + if (!ent->client) + return; + + P_Menu_Banned_InitEntries(); + P_Menu_Open(ent, banned_menu_entries, -1, sizeof(banned_menu_entries) / sizeof(menu_t), nullptr, P_Menu_Banned_Update); +} + +bool P_Menu_IsBannedMenu(const menu_hnd_t *hnd) { + if (!hnd) + return false; + + return hnd->UpdateFunc == P_Menu_Banned_Update; +} diff --git a/src/p_menu.h b/src/p_menu.h index 295a074..7b27561 100644 --- a/src/p_menu.h +++ b/src/p_menu.h @@ -37,3 +37,5 @@ void P_Menu_Update(gentity_t *ent); void P_Menu_Next(gentity_t *ent); void P_Menu_Prev(gentity_t *ent); void P_Menu_Select(gentity_t *ent); +void P_Menu_OpenBanned(gentity_t *ent); +bool P_Menu_IsBannedMenu(const menu_hnd_t *hnd); From f42c0201a4a166b324f104894d89516df3410921 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Mon, 6 Oct 2025 23:53:32 +0100 Subject: [PATCH 005/142] Fix PM_GetWaterLevel sampling height --- src/p_move.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index 76b27bb..f1c5817 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -854,23 +854,24 @@ static inline void PM_GetWaterLevel(const vec3_t &position, water_level_t &level level = WATER_NONE; type = CONTENTS_NONE; - int32_t sample2 = (int)(pm->s.viewheight - pm->mins[2]); - int32_t sample1 = sample2 / 2; + int32_t sample2 = (int)(pm->s.viewheight - pm->mins[2]); + int32_t sample1 = sample2 / 2; - vec3_t point = position; + vec3_t point = position; + float baseZ = position[2]; - point[2] += pm->mins[2] + 1; + point[2] = baseZ + pm->mins[2] + 1; contents_t cont = pm->pointcontents(point); if (cont & MASK_WATER) { type = cont; level = WATER_FEET; - point[2] = pml.origin[2] + pm->mins[2] + sample1; - cont = pm->pointcontents(point); - if (cont & MASK_WATER) { - level = WATER_WAIST; - point[2] = pml.origin[2] + pm->mins[2] + sample2; + point[2] = baseZ + pm->mins[2] + sample1; + cont = pm->pointcontents(point); + if (cont & MASK_WATER) { + level = WATER_WAIST; + point[2] = baseZ + pm->mins[2] + sample2; cont = pm->pointcontents(point); if (cont & MASK_WATER) level = WATER_UNDER; From e08e77d1101e609dd2cf41470c03e21947838341 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Mon, 6 Oct 2025 23:58:50 +0100 Subject: [PATCH 006/142] Restore legacy pmove snapping and revert z-origin allowances --- src/p_move.cpp | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index 76b27bb..5954766 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -427,8 +427,8 @@ static void PM_StepSlideMove() { down_o = pml.origin; down_v = pml.velocity; - up = start_o; - up[2] += (pml.origin[2] < 0) ? STEPSIZE_BELOW : STEPSIZE; + up = start_o; + up[2] += STEPSIZE; trace = PM_Trace(start_o, pm->mins, pm->maxs, up); if (trace.allsolid) @@ -484,10 +484,10 @@ static void PM_StepSlideMove() { pml.velocity[2] = down_v[2]; // Paril: step down stairs/slopes - if ((pm->s.pm_flags & PMF_ON_GROUND) && !(pm->s.pm_flags & PMF_ON_LADDER) && - (pm->waterlevel < WATER_WAIST || (!(pm->cmd.buttons & BUTTON_JUMP) && pml.velocity.z <= 0))) { - down = pml.origin; - down[2] -= (pml.origin[2] < 0) ? STEPSIZE_BELOW : STEPSIZE; + if ((pm->s.pm_flags & PMF_ON_GROUND) && !(pm->s.pm_flags & PMF_ON_LADDER) && + (pm->waterlevel < WATER_WAIST || (!(pm->cmd.buttons & BUTTON_JUMP) && pml.velocity.z <= 0))) { + down = pml.origin; + down[2] -= STEPSIZE; trace = PM_Trace(pml.origin, pm->mins, pm->maxs, down); if (trace.fraction < 1.f) { pml.origin = trace.endpos; @@ -998,10 +998,7 @@ static void PM_CheckJump() { pm->groundentity = nullptr; pm->s.pm_flags &= ~PMF_ON_GROUND; - float jump_height = 270.f; - - if (pml.origin[2] < 0) - jump_height += 4.0f; + float jump_height = 270.f; pml.velocity[2] = ceil(pml.velocity[2] + jump_height); if (pml.velocity[2] < jump_height) @@ -1087,8 +1084,8 @@ static void PM_CheckSpecialMovement() { // we're currently standing on ground, and the snapped position // is a step - if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= ((pml.origin[2] < 0) ? STEPSIZE_BELOW : STEPSIZE)) - return; + if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= STEPSIZE) + return; water_level_t level; contents_t type; @@ -1328,6 +1325,15 @@ bool PM_GoodPosition() { return !trace.allsolid; } +static vec3_t PM_SnapToLegacyGrid(const vec3_t &value) { + vec3_t snapped{}; + + for (size_t i = 0; i < 3; i++) + snapped[i] = (float)((int32_t)(value[i] * 8.0f)) * 0.125f; + + return snapped; +} + /* ================ PM_SnapPosition @@ -1337,8 +1343,11 @@ precision of the network channel and in a valid position. ================ */ static void PM_SnapPosition() { - pm->s.velocity = pml.velocity; - pm->s.origin = pml.origin; + pml.origin = PM_SnapToLegacyGrid(pml.origin); + pml.velocity = PM_SnapToLegacyGrid(pml.velocity); + + pm->s.velocity = pml.velocity; + pm->s.origin = pml.origin; if (PM_GoodPosition()) return; From 45bb8ec86fda26863d814945bbbca1305f830e42 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 7 Oct 2025 00:29:38 +0100 Subject: [PATCH 007/142] Use legacy ground probe for trick and ramp jumps --- src/p_move.cpp | 169 ++++++++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index 2063877..abf3d2c 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -879,91 +879,114 @@ static inline void PM_GetWaterLevel(const vec3_t &position, water_level_t &level } } -/* -============= -PM_CatagorizePosition -============= -*/ -static void PM_CatagorizePosition() { - vec3_t point; - trace_t trace; - - // if the player hull point one unit down is solid, the player - // is on ground - - // see if standing on something solid - point[0] = pml.origin[0]; - point[1] = pml.origin[1]; - point[2] = pml.origin[2] - 0.25f; - - if (pml.velocity[2] > 180 || pm->s.pm_type == PM_GRAPPLE) //!!ZOID changed from 100 to 180 (ramp accel) - { - pm->s.pm_flags &= ~PMF_ON_GROUND; - pm->groundentity = nullptr; - } else { - trace = PM_Trace(pml.origin, pm->mins, pm->maxs, point); - pm->groundplane = trace.plane; - pml.groundsurface = trace.surface; - pml.groundcontents = trace.contents; - - // [Paril-KEX] to attempt to fix edge cases where you get stuck - // wedged between a slope and a wall (which is irrecoverable - // most of the time), we'll allow the player to "stand" on - // slopes if they are right up against a wall - bool slanted_ground = trace.fraction < 1.0f && trace.plane.normal[2] < 0.7f; - - if (slanted_ground) { - trace_t slant = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + trace.plane.normal); - - if (slant.fraction < 1.0f && !slant.startsolid) - slanted_ground = false; - } +struct legacy_ground_result_t { + bool ramp_release = false; + bool on_ground = false; + bool trick_window = false; + float vertical_velocity = 0.0f; + trace_t trace; +}; - if (trace.fraction == 1.0f || (slanted_ground && !trace.startsolid)) { - pm->groundentity = nullptr; - pm->s.pm_flags &= ~PMF_ON_GROUND; - } else { - pm->groundentity = trace.ent; +static legacy_ground_result_t PM_QueryLegacyGround(bool was_on_ground) { + legacy_ground_result_t result{}; - // hitting solid ground will end a waterjump - if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { - pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); - pm->s.pm_time = 0; - } + result.vertical_velocity = pml.velocity[2]; - if (!(pm->s.pm_flags & PMF_ON_GROUND)) { - // just hit the ground + if (pml.velocity[2] > 180 || pm->s.pm_type == PM_GRAPPLE) { + result.ramp_release = true; + return result; + } - // [Paril-KEX] - if (!pm_config.n64_physics && pml.velocity[2] >= 100.f && pm->groundplane.normal[2] >= 0.9f && !(pm->s.pm_flags & PMF_DUCKED)) { - pm->s.pm_flags |= PMF_TIME_TRICK; - pm->s.pm_time = 64; - } + vec3_t point = pml.origin; + point[2] -= 0.25f; - // [Paril-KEX] calculate impact delta; this also fixes triple jumping - vec3_t clipped_velocity; - PM_ClipVelocity(pml.velocity, pm->groundplane.normal, clipped_velocity, 1.01f); + result.trace = PM_Trace(pml.origin, pm->mins, pm->maxs, point); - pm->impact_delta = pml.start_velocity[2] - clipped_velocity[2]; + if (!result.trace.ent || (result.trace.plane.normal[2] < 0.7f && !result.trace.startsolid)) + return result; - pm->s.pm_flags |= PMF_ON_GROUND; + result.on_ground = true; - if (pm_config.n64_physics || (pm->s.pm_flags & PMF_DUCKED)) { - pm->s.pm_flags |= PMF_TIME_LAND; - pm->s.pm_time = 128; - } - } - } + if (!was_on_ground && result.vertical_velocity > 0.0f) + result.trick_window = true; - PM_RecordTrace(pm->touch, trace); - } + return result; +} - // - // get waterlevel, accounting for ducking - // - PM_GetWaterLevel(pml.origin, pm->waterlevel, pm->watertype); +/* +============= +PM_CatagorizePosition +============= +*/ +static void PM_CatagorizePosition() { + bool was_on_ground = (pm->s.pm_flags & PMF_ON_GROUND); + legacy_ground_result_t legacy = PM_QueryLegacyGround(was_on_ground); + + if (legacy.ramp_release) { + pm->s.pm_flags &= ~PMF_ON_GROUND; + pm->groundentity = nullptr; + } else { + trace_t trace = legacy.trace; + pm->groundplane = trace.plane; + pml.groundsurface = trace.surface; + pml.groundcontents = trace.contents; + + // [Paril-KEX] to attempt to fix edge cases where you get stuck + // wedged between a slope and a wall (which is irrecoverable + // most of the time), we'll allow the player to "stand" on + // slopes if they are right up against a wall + bool slanted_ground = trace.fraction < 1.0f && trace.plane.normal[2] < 0.7f; + + if (slanted_ground) { + trace_t slant = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + trace.plane.normal); + + if (slant.fraction < 1.0f && !slant.startsolid) + slanted_ground = false; + } + + if (trace.fraction == 1.0f || (slanted_ground && !trace.startsolid)) { + pm->groundentity = nullptr; + pm->s.pm_flags &= ~PMF_ON_GROUND; + } else { + pm->groundentity = trace.ent; + + // hitting solid ground will end a waterjump + if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { + pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); + pm->s.pm_time = 0; + } + + if (!was_on_ground) { + if (!pm_config.n64_physics && legacy.trick_window && legacy.vertical_velocity >= 100.f && !(pm->s.pm_flags & PMF_DUCKED)) { + pm->s.pm_flags |= PMF_TIME_TRICK; + pm->s.pm_time = 64; + } + + // [Paril-KEX] calculate impact delta; this also fixes triple jumping + vec3_t clipped_velocity; + PM_ClipVelocity(pml.velocity, pm->groundplane.normal, clipped_velocity, 1.01f); + + pm->impact_delta = pml.start_velocity[2] - clipped_velocity[2]; + + pm->s.pm_flags |= PMF_ON_GROUND; + + if (pm_config.n64_physics || (pm->s.pm_flags & PMF_DUCKED)) { + pm->s.pm_flags |= PMF_TIME_LAND; + pm->s.pm_time = 128; + } + } + } + + PM_RecordTrace(pm->touch, trace); + } + + // + // get waterlevel, accounting for ducking + // + PM_GetWaterLevel(pml.origin, pm->waterlevel, pm->watertype); } + /* ============= PM_CheckJump From 047ff6f73756dd2d257e9964e29aa480a4664a97 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 7 Oct 2025 00:31:50 +0100 Subject: [PATCH 008/142] Enhance user ban checks and improve code readability Updated `CheckBanned` in `p_client.cpp` to support both "Steamworks" and "EOS" prefixes, including a specific case for a user ID. Refactored `p_move.cpp` for consistent formatting and improved readability across multiple functions, including `G_FixStuckObject_Generic`, `PM_ClipVelocity`, `PM_Trace`, and others. Overall, the changes focus on enhancing code clarity and maintainability while adding specific logic to the ban-checking functionality. --- src/p_client.cpp | 20 +++++- src/p_move.cpp | 156 ++++++++++++++++++++++++++--------------------- 2 files changed, 107 insertions(+), 69 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index 7bb4a2f..3172a18 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -3505,10 +3505,28 @@ static inline bool CheckBanned(gentity_t* ent, char* userinfo, const char* socia ent->client->sess.is_888 = false; - // currently all bans are in Steamworks, don't bother if not from there (or EOS mirrors) + // currently all bans are in Steamworks and EOS, don't bother if not from there if (!has_steam_prefix && !has_eos_prefix) return false; +#if 0 + // thmmuffinator test + if (!Q_strcasecmp(social_id, "Galaxy-198182751599832025")) { + gi.Info_SetValueForKey(userinfo, "rejmsg", "whoops it's the muff man!\n"); + + gentity_t* host = &g_entities[1]; + if (host && host->client) { + if (level.time > host->client->last_banned_message_time + 10_sec) { + + char name[MAX_INFO_VALUE] = { 0 }; + gi.Info_ValueForKey(userinfo, "name", name, sizeof(name)); + gi.LocClient_Print(host, PRINT_TTS, "MUFFY MUFFY MUFF MAN ({})!\n", name); + host->client->last_banned_message_time = level.time; + } + } + return true; + } +#endif // Israel if (!Q_strcasecmp(social_id, "Steamworks-76561198026297488")) { gi.Info_SetValueForKey(userinfo, "rejmsg", "Antisemite detected!\n"); diff --git a/src/p_move.cpp b/src/p_move.cpp index 2063877..a2bab93 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -7,7 +7,7 @@ #include "bg_local.h" // [Paril-KEX] generic code to detect & fix a stuck object -stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, const vec3_t &own_maxs, std::function trace) { +stuck_result_t G_FixStuckObject_Generic(vec3_t& origin, const vec3_t& own_mins, const vec3_t& own_maxs, std::function trace) { if (!trace(origin, own_mins, own_maxs, origin).startsolid) return stuck_result_t::GOOD_POSITION; @@ -30,7 +30,7 @@ stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, }; for (size_t sn = 0; sn < q_countof(side_checks); sn++) { - auto &side = side_checks[sn]; + auto& side = side_checks[sn]; vec3_t start = origin; vec3_t mins{}, maxs{}; @@ -90,7 +90,7 @@ stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, continue; vec3_t opposite_start = origin; - auto &other_side = side_checks[sn ^ 1]; + auto& other_side = side_checks[sn ^ 1]; for (size_t n = 0; n < 3; n++) { if (other_side.normal[n] < 0) @@ -134,7 +134,7 @@ stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, } if (num_good_positions) { - std::sort(&good_positions[0], &good_positions[num_good_positions - 1], [](const auto &a, const auto &b) { return a.distance < b.distance; }); + std::sort(&good_positions[0], &good_positions[num_good_positions - 1], [](const auto& a, const auto& b) { return a.distance < b.distance; }); origin = good_positions[0].origin; @@ -155,7 +155,7 @@ struct pml_t { vec3_t forward, right, up; float frametime; - csurface_t *groundsurface; + csurface_t* groundsurface; int groundcontents; vec3_t previous_origin; @@ -164,7 +164,7 @@ struct pml_t { pm_config_t pm_config; -pmove_t *pm; +pmove_t* pm; pml_t pml; // movement parameters @@ -184,7 +184,7 @@ float pm_laddermod = 0.5f; */ -static float MaxSpeed(pmove_state_t *ps) { +static float MaxSpeed(pmove_state_t* ps) { return ps->haste ? pm_maxspeed * 1.3 : pm_maxspeed; } @@ -196,7 +196,7 @@ Slide off of the impacting object returns the blocked flags (1 = floor, 2 = step / wall) ================== */ -static void PM_ClipVelocity(const vec3_t &in, const vec3_t &normal, vec3_t &out, float overbounce) { +static void PM_ClipVelocity(const vec3_t& in, const vec3_t& normal, vec3_t& out, float overbounce) { float backoff; float change; int i; @@ -211,11 +211,11 @@ static void PM_ClipVelocity(const vec3_t &in, const vec3_t &normal, vec3_t &out, } } -static trace_t PM_Clip(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, contents_t mask) { +static trace_t PM_Clip(const vec3_t& start, const vec3_t& mins, const vec3_t& maxs, const vec3_t& end, contents_t mask) { return pm->clip(start, &mins, &maxs, end, mask); } -static trace_t PM_Trace(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end, contents_t mask = CONTENTS_NONE) { +static trace_t PM_Trace(const vec3_t& start, const vec3_t& mins, const vec3_t& maxs, const vec3_t& end, contents_t mask = CONTENTS_NONE) { if (pm->s.pm_type == PM_SPECTATOR) return PM_Clip(start, mins, maxs, end, MASK_SOLID); @@ -235,7 +235,7 @@ static trace_t PM_Trace(const vec3_t &start, const vec3_t &mins, const vec3_t &m } // only here to satisfy pm_trace_t -static inline trace_t PM_Trace_Auto(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { +static inline trace_t PM_Trace_Auto(const vec3_t& start, const vec3_t& mins, const vec3_t& maxs, const vec3_t& end) { return PM_Trace(start, mins, maxs, end); } @@ -253,7 +253,7 @@ Does not modify any world state? constexpr float MIN_STEP_NORMAL = 0.7f; // can't step up onto very steep slopes constexpr size_t MAX_CLIP_PLANES = 5; -static inline void PM_RecordTrace(touch_list_t &touch, trace_t &tr) { +static inline void PM_RecordTrace(touch_list_t& touch, trace_t& tr) { if (touch.num == MAXTOUCH) return; @@ -266,7 +266,7 @@ static inline void PM_RecordTrace(touch_list_t &touch, trace_t &tr) { // [Paril-KEX] made generic so you can run this without // needing a pml/pm -void PM_StepSlideMove_Generic(vec3_t &origin, vec3_t &velocity, float frametime, const vec3_t &mins, const vec3_t &maxs, touch_list_t &touch, bool has_time, pm_trace_t trace_func) { +void PM_StepSlideMove_Generic(vec3_t& origin, vec3_t& velocity, float frametime, const vec3_t& mins, const vec3_t& maxs, touch_list_t& touch, bool has_time, pm_trace_t trace_func) { int bumpcount, numbumps; vec3_t dir; float d; @@ -376,7 +376,8 @@ void PM_StepSlideMove_Generic(vec3_t &origin, vec3_t &velocity, float frametime, } if (i != numplanes) { // go along this plane - } else { // go along the crease + } + else { // go along the crease if (numplanes != 2) { velocity = vec3_origin; break; @@ -426,9 +427,9 @@ static void PM_StepSlideMove() { down_o = pml.origin; down_v = pml.velocity; - - up = start_o; - up[2] += STEPSIZE; + + up = start_o; + up[2] += STEPSIZE; trace = PM_Trace(start_o, pm->mins, pm->maxs, up); if (trace.allsolid) @@ -484,10 +485,10 @@ static void PM_StepSlideMove() { pml.velocity[2] = down_v[2]; // Paril: step down stairs/slopes - if ((pm->s.pm_flags & PMF_ON_GROUND) && !(pm->s.pm_flags & PMF_ON_LADDER) && - (pm->waterlevel < WATER_WAIST || (!(pm->cmd.buttons & BUTTON_JUMP) && pml.velocity.z <= 0))) { - down = pml.origin; - down[2] -= STEPSIZE; + if ((pm->s.pm_flags & PMF_ON_GROUND) && !(pm->s.pm_flags & PMF_ON_LADDER) && + (pm->waterlevel < WATER_WAIST || (!(pm->cmd.buttons & BUTTON_JUMP) && pml.velocity.z <= 0))) { + down = pml.origin; + down[2] -= STEPSIZE; trace = PM_Trace(pml.origin, pm->mins, pm->maxs, down); if (trace.fraction < 1.f) { pml.origin = trace.endpos; @@ -503,7 +504,7 @@ Handles both ground friction and water friction ================== */ static void PM_Friction() { - float *vel; + float* vel; float speed, newspeed, control; float friction; float drop; @@ -549,7 +550,7 @@ PM_Accelerate Handles user intended acceleration ============== */ -static void PM_Accelerate(const vec3_t &wishdir, float wishspeed, float accel) { +static void PM_Accelerate(const vec3_t& wishdir, float wishspeed, float accel) { int i; float addspeed, accelspeed, currentspeed; @@ -565,7 +566,7 @@ static void PM_Accelerate(const vec3_t &wishdir, float wishspeed, float accel) { pml.velocity[i] += accelspeed * wishdir[i]; } -static void PM_AirAccelerate(const vec3_t &wishdir, float wishspeed, float accel) { +static void PM_AirAccelerate(const vec3_t& wishdir, float wishspeed, float accel) { int i; float addspeed, accelspeed, currentspeed, wishspd = wishspeed; @@ -588,7 +589,7 @@ static void PM_AirAccelerate(const vec3_t &wishdir, float wishspeed, float accel PM_AddCurrents ============= */ -static void PM_AddCurrents(vec3_t &wishvel) { +static void PM_AddCurrents(vec3_t& wishvel) { vec3_t v; float s; @@ -605,7 +606,8 @@ static void PM_AddCurrents(vec3_t &wishvel) { wishvel[2] = ladder_speed; else if (pm->cmd.buttons & BUTTON_CROUCH) wishvel[2] = -ladder_speed; - } else if (pm->cmd.forwardmove) { + } + else if (pm->cmd.forwardmove) { // [Paril-KEX] clamp the speed a bit so we're not too fast float ladder_speed = std::clamp(pm->cmd.forwardmove, -200.f, 200.f); @@ -624,7 +626,8 @@ static void PM_AddCurrents(vec3_t &wishvel) { wishvel[2] = ladder_speed; } - } else + } + else wishvel[2] = 0; // limit horizontal speed when on a ladder @@ -655,7 +658,8 @@ static void PM_AddCurrents(vec3_t &wishvel) { wishvel[0] = wishvel[1] = 0; wishvel += (right * -ladder_speed); } - } else { + } + else { if (wishvel[0] < -25) wishvel[0] = -25; else if (wishvel[0] > 25) @@ -743,7 +747,8 @@ static void PM_WaterMove() { !(pm->cmd.buttons & (BUTTON_JUMP | BUTTON_CROUCH))) { if (!pm->groundentity) wishvel[2] -= 60; // drift towards bottom - } else { + } + else { if (pm->cmd.buttons & BUTTON_CROUCH) wishvel[2] -= pm_waterspeed * 0.5f; else if (pm->cmd.buttons & BUTTON_JUMP) @@ -814,14 +819,16 @@ static void PM_AirMove() { pml.velocity[2] -= pm->s.gravity * pml.frametime; if (pml.velocity[2] < 0) pml.velocity[2] = 0; - } else { + } + else { pml.velocity[2] += pm->s.gravity * pml.frametime; if (pml.velocity[2] > 0) pml.velocity[2] = 0; } } PM_StepSlideMove(); - } else if (pm->groundentity) { // walking on ground + } + else if (pm->groundentity) { // walking on ground pml.velocity[2] = 0; //!!! this is before the accel PM_Accelerate(wishdir, wishspeed, pm_accelerate); @@ -833,7 +840,8 @@ static void PM_AirMove() { if (!pml.velocity[0] && !pml.velocity[1]) return; PM_StepSlideMove(); - } else { // not on ground, so little effect on velocity + } + else { // not on ground, so little effect on velocity if (pm_config.airaccel) PM_AirAccelerate(wishdir, wishspeed, pm_config.airaccel); else @@ -847,31 +855,31 @@ static void PM_AirMove() { } } -static inline void PM_GetWaterLevel(const vec3_t &position, water_level_t &level, contents_t &type) { +static inline void PM_GetWaterLevel(const vec3_t& position, water_level_t& level, contents_t& type) { // // get waterlevel, accounting for ducking // level = WATER_NONE; type = CONTENTS_NONE; - int32_t sample2 = (int)(pm->s.viewheight - pm->mins[2]); - int32_t sample1 = sample2 / 2; + int32_t sample2 = (int)(pm->s.viewheight - pm->mins[2]); + int32_t sample1 = sample2 / 2; - vec3_t point = position; - float baseZ = position[2]; + vec3_t point = position; + float baseZ = position[2]; - point[2] = baseZ + pm->mins[2] + 1; + point[2] = baseZ + pm->mins[2] + 1; contents_t cont = pm->pointcontents(point); if (cont & MASK_WATER) { type = cont; level = WATER_FEET; - point[2] = baseZ + pm->mins[2] + sample1; - cont = pm->pointcontents(point); - if (cont & MASK_WATER) { - level = WATER_WAIST; - point[2] = baseZ + pm->mins[2] + sample2; + point[2] = baseZ + pm->mins[2] + sample1; + cont = pm->pointcontents(point); + if (cont & MASK_WATER) { + level = WATER_WAIST; + point[2] = baseZ + pm->mins[2] + sample2; cont = pm->pointcontents(point); if (cont & MASK_WATER) level = WATER_UNDER; @@ -900,7 +908,8 @@ static void PM_CatagorizePosition() { { pm->s.pm_flags &= ~PMF_ON_GROUND; pm->groundentity = nullptr; - } else { + } + else { trace = PM_Trace(pml.origin, pm->mins, pm->maxs, point); pm->groundplane = trace.plane; pml.groundsurface = trace.surface; @@ -922,7 +931,8 @@ static void PM_CatagorizePosition() { if (trace.fraction == 1.0f || (slanted_ground && !trace.startsolid)) { pm->groundentity = nullptr; pm->s.pm_flags &= ~PMF_ON_GROUND; - } else { + } + else { pm->groundentity = trace.ent; // hitting solid ground will end a waterjump @@ -999,7 +1009,7 @@ static void PM_CheckJump() { pm->groundentity = nullptr; pm->s.pm_flags &= ~PMF_ON_GROUND; - float jump_height = 270.f; + float jump_height = 270.f; pml.velocity[2] = ceil(pml.velocity[2] + jump_height); if (pml.velocity[2] < jump_height) @@ -1085,8 +1095,8 @@ static void PM_CheckSpecialMovement() { // we're currently standing on ground, and the snapped position // is a step - if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= STEPSIZE) - return; + if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= STEPSIZE) + return; water_level_t level; contents_t type; @@ -1129,7 +1139,8 @@ static void PM_FlyMove(bool doclip) { speed = pml.velocity.length(); if (speed < 1) { pml.velocity = vec3_origin; - } else { + } + else { drop = 0; friction = pm_friction * 1.5f; // extra friction @@ -1195,7 +1206,8 @@ static void PM_FlyMove(bool doclip) { pml.origin = trace.endpos;*/ PM_StepSlideMove(); - } else { + } + else { // move pml.origin += (pml.velocity * pml.frametime); } @@ -1220,7 +1232,8 @@ static void PM_SetDimensions() { if ((pm->s.pm_flags & PMF_DUCKED) || pm->s.pm_type == PM_DEAD) { pm->maxs[2] = 4; pm->s.viewheight = -2; - } else { + } + else { pm->maxs[2] = 32; pm->s.viewheight = 22; } @@ -1261,7 +1274,8 @@ static bool PM_CheckDuck() { pm->s.pm_flags |= PMF_DUCKED; flags_changed = true; } - } else if ( + } + else if ( (pm->cmd.buttons & BUTTON_CROUCH) && (pm->groundentity || (pm->waterlevel <= WATER_FEET && !PM_AboveWater())) && !(pm->s.pm_flags & PMF_ON_LADDER) && @@ -1275,7 +1289,8 @@ static bool PM_CheckDuck() { flags_changed = true; } } - } else { // stand up if possible + } + else { // stand up if possible if (pm->s.pm_flags & PMF_DUCKED) { // try to stand up vec3_t check_maxs = { pm->maxs[0], pm->maxs[1], 32 }; @@ -1311,7 +1326,8 @@ static void PM_DeadMove() { forward -= 20; if (forward <= 0) { pml.velocity = {}; - } else { + } + else { pml.velocity.normalize(); pml.velocity *= forward; } @@ -1326,13 +1342,13 @@ bool PM_GoodPosition() { return !trace.allsolid; } -static vec3_t PM_SnapToLegacyGrid(const vec3_t &value) { - vec3_t snapped{}; +static vec3_t PM_SnapToLegacyGrid(const vec3_t& value) { + vec3_t snapped{}; - for (size_t i = 0; i < 3; i++) - snapped[i] = (float)((int32_t)(value[i] * 8.0f)) * 0.125f; + for (size_t i = 0; i < 3; i++) + snapped[i] = (float)((int32_t)(value[i] * 8.0f)) * 0.125f; - return snapped; + return snapped; } /* @@ -1344,11 +1360,11 @@ precision of the network channel and in a valid position. ================ */ static void PM_SnapPosition() { - pml.origin = PM_SnapToLegacyGrid(pml.origin); - pml.velocity = PM_SnapToLegacyGrid(pml.velocity); + pml.origin = PM_SnapToLegacyGrid(pml.origin); + pml.velocity = PM_SnapToLegacyGrid(pml.velocity); - pm->s.velocity = pml.velocity; - pm->s.origin = pml.origin; + pm->s.velocity = pml.velocity; + pm->s.origin = pml.origin; if (PM_GoodPosition()) return; @@ -1399,7 +1415,8 @@ static void PM_ClampAngles() { pm->viewangles[YAW] = pm->cmd.angles[YAW] + pm->s.delta_angles[YAW]; pm->viewangles[PITCH] = 0; pm->viewangles[ROLL] = 0; - } else { + } + else { // circularly clamp the angles with deltas pm->viewangles = pm->cmd.angles + pm->s.delta_angles; @@ -1438,7 +1455,7 @@ Pmove Can be called by either the server or the client ================ */ -void Pmove(pmove_t *pmove) { +void Pmove(pmove_t* pmove) { pm = pmove; // clear results @@ -1519,12 +1536,14 @@ void Pmove(pmove_t *pmove) { if (pm->cmd.msec >= pm->s.pm_time) { pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); pm->s.pm_time = 0; - } else + } + else pm->s.pm_time -= pm->cmd.msec; } if (pm->s.pm_flags & PMF_TIME_TELEPORT) { // teleport pause stays exactly in place - } else if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { // waterjump has no control, but falls + } + else if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { // waterjump has no control, but falls pml.velocity[2] -= pm->s.gravity * pml.frametime; if (pml.velocity[2] < 0) { // cancel as soon as we are falling down again pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); @@ -1532,7 +1551,8 @@ void Pmove(pmove_t *pmove) { } PM_StepSlideMove(); - } else { + } + else { PM_CheckJump(); PM_Friction(); From 6726ccf8e8cf7d3bc46ff8311862a0f92a2b8cd9 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 7 Oct 2025 01:43:08 +0100 Subject: [PATCH 009/142] revert previous changes --- src/p_move.cpp | 153 +++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 87 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index f7dbf44..a2bab93 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -887,114 +887,93 @@ static inline void PM_GetWaterLevel(const vec3_t& position, water_level_t& level } } -struct legacy_ground_result_t { - bool ramp_release = false; - bool on_ground = false; - bool trick_window = false; - float vertical_velocity = 0.0f; - trace_t trace; -}; - -static legacy_ground_result_t PM_QueryLegacyGround(bool was_on_ground) { - legacy_ground_result_t result{}; - - result.vertical_velocity = pml.velocity[2]; - - if (pml.velocity[2] > 180 || pm->s.pm_type == PM_GRAPPLE) { - result.ramp_release = true; - return result; - } - - vec3_t point = pml.origin; - point[2] -= 0.25f; - - result.trace = PM_Trace(pml.origin, pm->mins, pm->maxs, point); - - if (!result.trace.ent || (result.trace.plane.normal[2] < 0.7f && !result.trace.startsolid)) - return result; - - result.on_ground = true; - - if (!was_on_ground && result.vertical_velocity > 0.0f) - result.trick_window = true; - - return result; -} - /* ============= PM_CatagorizePosition ============= */ static void PM_CatagorizePosition() { - bool was_on_ground = (pm->s.pm_flags & PMF_ON_GROUND); - legacy_ground_result_t legacy = PM_QueryLegacyGround(was_on_ground); - - if (legacy.ramp_release) { - pm->s.pm_flags &= ~PMF_ON_GROUND; - pm->groundentity = nullptr; - } else { - trace_t trace = legacy.trace; - pm->groundplane = trace.plane; - pml.groundsurface = trace.surface; - pml.groundcontents = trace.contents; - - // [Paril-KEX] to attempt to fix edge cases where you get stuck - // wedged between a slope and a wall (which is irrecoverable - // most of the time), we'll allow the player to "stand" on - // slopes if they are right up against a wall - bool slanted_ground = trace.fraction < 1.0f && trace.plane.normal[2] < 0.7f; - - if (slanted_ground) { - trace_t slant = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + trace.plane.normal); - - if (slant.fraction < 1.0f && !slant.startsolid) - slanted_ground = false; - } + vec3_t point; + trace_t trace; + + // if the player hull point one unit down is solid, the player + // is on ground + + // see if standing on something solid + point[0] = pml.origin[0]; + point[1] = pml.origin[1]; + point[2] = pml.origin[2] - 0.25f; + + if (pml.velocity[2] > 180 || pm->s.pm_type == PM_GRAPPLE) //!!ZOID changed from 100 to 180 (ramp accel) + { + pm->s.pm_flags &= ~PMF_ON_GROUND; + pm->groundentity = nullptr; + } + else { + trace = PM_Trace(pml.origin, pm->mins, pm->maxs, point); + pm->groundplane = trace.plane; + pml.groundsurface = trace.surface; + pml.groundcontents = trace.contents; + + // [Paril-KEX] to attempt to fix edge cases where you get stuck + // wedged between a slope and a wall (which is irrecoverable + // most of the time), we'll allow the player to "stand" on + // slopes if they are right up against a wall + bool slanted_ground = trace.fraction < 1.0f && trace.plane.normal[2] < 0.7f; + + if (slanted_ground) { + trace_t slant = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + trace.plane.normal); + + if (slant.fraction < 1.0f && !slant.startsolid) + slanted_ground = false; + } if (trace.fraction == 1.0f || (slanted_ground && !trace.startsolid)) { pm->groundentity = nullptr; pm->s.pm_flags &= ~PMF_ON_GROUND; - } else { + } + else { pm->groundentity = trace.ent; - // hitting solid ground will end a waterjump - if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { - pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); - pm->s.pm_time = 0; - } + // hitting solid ground will end a waterjump + if (pm->s.pm_flags & PMF_TIME_WATERJUMP) { + pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT | PMF_TIME_TRICK); + pm->s.pm_time = 0; + } + + if (!(pm->s.pm_flags & PMF_ON_GROUND)) { + // just hit the ground - if (!was_on_ground) { - if (!pm_config.n64_physics && legacy.trick_window && legacy.vertical_velocity >= 100.f && !(pm->s.pm_flags & PMF_DUCKED)) { - pm->s.pm_flags |= PMF_TIME_TRICK; - pm->s.pm_time = 64; - } + // [Paril-KEX] + if (!pm_config.n64_physics && pml.velocity[2] >= 100.f && pm->groundplane.normal[2] >= 0.9f && !(pm->s.pm_flags & PMF_DUCKED)) { + pm->s.pm_flags |= PMF_TIME_TRICK; + pm->s.pm_time = 64; + } - // [Paril-KEX] calculate impact delta; this also fixes triple jumping - vec3_t clipped_velocity; - PM_ClipVelocity(pml.velocity, pm->groundplane.normal, clipped_velocity, 1.01f); + // [Paril-KEX] calculate impact delta; this also fixes triple jumping + vec3_t clipped_velocity; + PM_ClipVelocity(pml.velocity, pm->groundplane.normal, clipped_velocity, 1.01f); - pm->impact_delta = pml.start_velocity[2] - clipped_velocity[2]; + pm->impact_delta = pml.start_velocity[2] - clipped_velocity[2]; - pm->s.pm_flags |= PMF_ON_GROUND; + pm->s.pm_flags |= PMF_ON_GROUND; - if (pm_config.n64_physics || (pm->s.pm_flags & PMF_DUCKED)) { - pm->s.pm_flags |= PMF_TIME_LAND; - pm->s.pm_time = 128; - } - } - } + if (pm_config.n64_physics || (pm->s.pm_flags & PMF_DUCKED)) { + pm->s.pm_flags |= PMF_TIME_LAND; + pm->s.pm_time = 128; + } + } + } - PM_RecordTrace(pm->touch, trace); - } + PM_RecordTrace(pm->touch, trace); + } - // - // get waterlevel, accounting for ducking - // - PM_GetWaterLevel(pml.origin, pm->waterlevel, pm->watertype); + // + // get waterlevel, accounting for ducking + // + PM_GetWaterLevel(pml.origin, pm->waterlevel, pm->watertype); } - /* ============= PM_CheckJump From b9ede4049a33202af7a72603f105464beacd2d8c Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 7 Oct 2025 01:45:49 +0100 Subject: [PATCH 010/142] revert to previous changes --- src/p_move.cpp | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index a2bab93..7437d3c 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -429,7 +429,7 @@ static void PM_StepSlideMove() { down_v = pml.velocity; up = start_o; - up[2] += STEPSIZE; + up[2] += (pml.origin[2] < 0) ? STEPSIZE_BELOW : STEPSIZE; trace = PM_Trace(start_o, pm->mins, pm->maxs, up); if (trace.allsolid) @@ -488,7 +488,7 @@ static void PM_StepSlideMove() { if ((pm->s.pm_flags & PMF_ON_GROUND) && !(pm->s.pm_flags & PMF_ON_LADDER) && (pm->waterlevel < WATER_WAIST || (!(pm->cmd.buttons & BUTTON_JUMP) && pml.velocity.z <= 0))) { down = pml.origin; - down[2] -= STEPSIZE; + down[2] -= (pml.origin[2] < 0) ? STEPSIZE_BELOW : STEPSIZE; trace = PM_Trace(pml.origin, pm->mins, pm->maxs, down); if (trace.fraction < 1.f) { pml.origin = trace.endpos; @@ -1011,6 +1011,9 @@ static void PM_CheckJump() { float jump_height = 270.f; + if (pml.origin[2] < 0) + jump_height += 4.0f; + pml.velocity[2] = ceil(pml.velocity[2] + jump_height); if (pml.velocity[2] < jump_height) pml.velocity[2] = jump_height; @@ -1095,7 +1098,7 @@ static void PM_CheckSpecialMovement() { // we're currently standing on ground, and the snapped position // is a step - if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= STEPSIZE) + if (pm->groundentity && fabsf(pml.origin.z - trace.endpos.z) <= ((pml.origin[2] < 0) ? STEPSIZE_BELOW : STEPSIZE)) return; water_level_t level; @@ -1342,15 +1345,6 @@ bool PM_GoodPosition() { return !trace.allsolid; } -static vec3_t PM_SnapToLegacyGrid(const vec3_t& value) { - vec3_t snapped{}; - - for (size_t i = 0; i < 3; i++) - snapped[i] = (float)((int32_t)(value[i] * 8.0f)) * 0.125f; - - return snapped; -} - /* ================ PM_SnapPosition @@ -1360,9 +1354,6 @@ precision of the network channel and in a valid position. ================ */ static void PM_SnapPosition() { - pml.origin = PM_SnapToLegacyGrid(pml.origin); - pml.velocity = PM_SnapToLegacyGrid(pml.velocity); - pm->s.velocity = pml.velocity; pm->s.origin = pml.origin; From aa77cd231c5d5183d7c1ac369d03ca603e1babbb Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 20:12:41 +0000 Subject: [PATCH 011/142] solution naming, target_teleporter safety check --- src/{game.sln => MuffMode.sln} | 0 src/g_target.cpp | 12 +++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) rename src/{game.sln => MuffMode.sln} (100%) diff --git a/src/game.sln b/src/MuffMode.sln similarity index 100% rename from src/game.sln rename to src/MuffMode.sln diff --git a/src/g_target.cpp b/src/g_target.cpp index 67b70ba..c887df9 100644 --- a/src/g_target.cpp +++ b/src/g_target.cpp @@ -2450,11 +2450,13 @@ static USE(target_teleporter_use) (gentity_t *ent, gentity_t *other, gentity_t * } void SP_target_teleporter(gentity_t *ent) { - - if (!ent->target[0]) { - //gi.Com_PrintFmt("{}: Couldn't find teleporter destination, removing.\n", ent); - //G_FreeEntity(ent); - //return; + if (ent->target && ent->target[0]) { + ent->target_ent = G_PickTarget(ent->target); + if (!ent->target_ent) { + gi.Com_PrintFmt("{}: Couldn't find teleporter destination, removing.\n", *ent); + G_FreeEntity(ent); + return; + } } ent->target_ent = G_PickTarget(ent->target); From 6b0ac83b3b91d7fc569c8340f4a4bf9762aff4bb Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 20:13:18 +0000 Subject: [PATCH 012/142] Add fmt formatter for gentity pointers --- src/g_local.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/g_local.h b/src/g_local.h index 908f428..aa0dcfa 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -4238,6 +4238,30 @@ struct fmt::formatter { } }; +template<> +struct fmt::formatter : fmt::formatter { + using fmt::formatter::parse; + + template + auto format(gentity_t *const &p, FormatContext &ctx) -> decltype(ctx.out()) { + if (!p) + return fmt::format_to(ctx.out(), FMT_STRING("null gentity_t")); + return fmt::formatter::format(*p, ctx); + } +}; + +template<> +struct fmt::formatter : fmt::formatter { + using fmt::formatter::parse; + + template + auto format(const gentity_t *const &p, FormatContext &ctx) -> decltype(ctx.out()) { + if (!p) + return fmt::format_to(ctx.out(), FMT_STRING("null gentity_t")); + return fmt::formatter::format(*p, ctx); + } +}; + // POI tags used by this mod enum pois_t : uint16_t { POI_OBJECTIVE = MAX_ENTITIES, // current objective From 87bfd13b88c9f43c57111548918b8398677b832a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 20:49:55 +0000 Subject: [PATCH 013/142] Harden menu vote handling and stats safety --- src/g_menu.cpp | 123 +++++++++++++++++++++++++++++++++++------------ src/p_client.cpp | 4 +- 2 files changed, 93 insertions(+), 34 deletions(-) diff --git a/src/g_menu.cpp b/src/g_menu.cpp index 23180e0..eae9c68 100644 --- a/src/g_menu.cpp +++ b/src/g_menu.cpp @@ -313,52 +313,86 @@ const menu_t pmstatsmenu[] = { static void G_Menu_PMStats_Update(gentity_t *ent) { - if (!g_matchstats->integer) return; + if (!g_matchstats->integer) + return; menu_t *entries = ent->client->menu->entries; client_match_stats_t *st = &ent->client->mstats; int i = 0; char value[MAX_INFO_VALUE] = { 0 }; - gi.Info_ValueForKey(g_entities[1].client->pers.userinfo, "name", value, sizeof(value)); + if (game.maxclients > 0 && g_entities[1].client) { + gi.Info_ValueForKey(g_entities[1].client->pers.userinfo, "name", value, sizeof(value)); + } - Q_strlcpy(entries[i].text, "Player Stats for Match", sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, "Player Stats for Match", sizeof(entries[i].text)); i++; if (value[0]) { - Q_strlcpy(entries[i].text, G_Fmt("{}", value).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("{}", value).data(), sizeof(entries[i].text)); i++; } - Q_strlcpy(entries[i].text, BREAKER, sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, BREAKER, sizeof(entries[i].text)); i++; - Q_strlcpy(entries[i].text, G_Fmt("kills: {}", st->total_kills).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("kills: {}", st->total_kills).data(), sizeof(entries[i].text)); i++; - Q_strlcpy(entries[i].text, G_Fmt("deaths: {}", st->total_deaths).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("deaths: {}", st->total_deaths).data(), sizeof(entries[i].text)); i++; if (st->total_kills) { - float val = st->total_kills > 0 ? ((float)st->total_kills / (float)st->total_deaths) : 0; - Q_strlcpy(entries[i].text, G_Fmt("k/d ratio: {:2}", val).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) { + if (st->total_deaths > 0) { + float val = (float)st->total_kills / (float)st->total_deaths; + Q_strlcpy(entries[i].text, G_Fmt("k/d ratio: {:2}", val).data(), sizeof(entries[i].text)); + } else { + Q_strlcpy(entries[i].text, "k/d ratio: N/A", sizeof(entries[i].text)); + } + } i++; } + if (i < ent->client->menu->num) + entries[i].text[0] = '\0'; i++; - Q_strlcpy(entries[i].text, G_Fmt("dmg dealt: {}", st->total_dmg_dealt).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("dmg dealt: {}", st->total_dmg_dealt).data(), sizeof(entries[i].text)); i++; - Q_strlcpy(entries[i].text, G_Fmt("dmg received: {}", st->total_dmg_received).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("dmg received: {}", st->total_dmg_received).data(), sizeof(entries[i].text)); i++; if (st->total_dmg_dealt) { - float val = st->total_dmg_dealt ? ((float)st->total_dmg_dealt / (float)st->total_dmg_received) : 0; - Q_strlcpy(entries[i].text, G_Fmt("dmg ratio: {:02}", val).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) { + if (st->total_dmg_received > 0) { + float val = (float)st->total_dmg_dealt / (float)st->total_dmg_received; + Q_strlcpy(entries[i].text, G_Fmt("dmg ratio: {:02}", val).data(), sizeof(entries[i].text)); + } else { + Q_strlcpy(entries[i].text, "dmg ratio: N/A", sizeof(entries[i].text)); + } + } i++; } + if (i < ent->client->menu->num) + entries[i].text[0] = '\0'; i++; - Q_strlcpy(entries[i].text, G_Fmt("shots fired: {}", st->total_shots).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("shots fired: {}", st->total_shots).data(), sizeof(entries[i].text)); i++; - Q_strlcpy(entries[i].text, G_Fmt("shots on target: {}", st->total_hits).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) + Q_strlcpy(entries[i].text, G_Fmt("shots on target: {}", st->total_hits).data(), sizeof(entries[i].text)); i++; if (st->total_hits) { - int val = st->total_hits ? ((float)st->total_hits / (float)st->total_shots) * 100. : 0; - Q_strlcpy(entries[i].text, G_Fmt("total accuracy: {}%", val).data(), sizeof(entries[i].text)); + if (i < ent->client->menu->num) { + if (st->total_shots > 0) { + int val = (int)(((float)st->total_hits / (float)st->total_shots) * 100.f); + Q_strlcpy(entries[i].text, G_Fmt("total accuracy: {}%", val).data(), sizeof(entries[i].text)); + } else { + Q_strlcpy(entries[i].text, "total accuracy: N/A", sizeof(entries[i].text)); + } + } i++; } } @@ -460,11 +494,24 @@ const menu_t pmcallvotemenu_timelimit[] = { }; void G_Menu_CallVote_Map_Selection(gentity_t *ent, menu_hnd_t *p) { - vcmds_t *cc = FindVoteCmdByName("map"); + if (!cc) { + gi.Com_PrintFmt("{}: missing map vote command.\n", __FUNCTION__); + return; + } + if (!p || !p->entries || p->cur < 0 || p->cur >= p->num) { + gi.Com_PrintFmt("{}: invalid map selection index.\n", __FUNCTION__); + return; + } + + const menu_t &selected = p->entries[p->cur]; + if (!selected.text[0]) { + gi.Com_PrintFmt("{}: no map selected.\n", __FUNCTION__); + return; + } level.vote = cc; - level.vote_arg = std::string("q2dm1"); //TODO: store selected map name for use here + level.vote_arg = selected.text; VoteCommandStore(ent); P_Menu_Close(ent); @@ -472,11 +519,21 @@ void G_Menu_CallVote_Map_Selection(gentity_t *ent, menu_hnd_t *p) { inline std::vector str_split(const std::string_view &str, char by) { std::vector out; - size_t start, end = 0; + size_t start = 0; + + while (true) { + start = str.find_first_not_of(by, start); + if (start == std::string_view::npos) + break; - while ((start = str.find_first_not_of(by, end)) != std::string_view::npos) { - end = str.find(by, start); - out.push_back(std::string{ str.substr(start, end - start) }); + size_t end = str.find(by, start); + if (end == std::string_view::npos) { + out.emplace_back(str.substr(start)); + break; + } + + out.emplace_back(str.substr(start, end - start)); + start = end + 1; } return out; @@ -493,10 +550,12 @@ static void G_Menu_CallVote_Map_Update(gentity_t *ent) { if (!values.size()) return; - for (i = 2; i < 15; i++) + for (i = 2; i < 15; i++) { entries[i].SelectFunc = nullptr; + entries[i].text[0] = '\0'; + } - for (num = 0, i = 2; num < values.size(), num < 15; num++, i++) { + for (num = 0, i = 2; num < values.size() && num < 15; num++, i++) { Q_strlcpy(entries[i].text, values[num].c_str(), sizeof(entries[i].text)); entries[i].SelectFunc = G_Menu_CallVote_Map_Selection; } @@ -509,14 +568,14 @@ void G_Menu_CallVote_Map(gentity_t *ent, menu_hnd_t *p) { void G_Menu_CallVote_NextMap(gentity_t *ent, menu_hnd_t *p) { level.vote = FindVoteCmdByName("nextmap"); - level.vote_arg = nullptr; + level.vote_arg.clear(); VoteCommandStore(ent); P_Menu_Close(ent); } void G_Menu_CallVote_Restart(gentity_t *ent, menu_hnd_t *p) { level.vote = FindVoteCmdByName("restart"); - level.vote_arg = nullptr; + level.vote_arg.clear(); VoteCommandStore(ent); P_Menu_Close(ent); } @@ -527,12 +586,12 @@ void G_Menu_CallVote_GameType(gentity_t *ent, menu_hnd_t *p) { void G_Menu_CallVote_TimeLimit_Update(gentity_t *ent) { - level.vote_arg = nullptr; + level.vote_arg.clear(); } void G_Menu_CallVote_TimeLimit(gentity_t *ent, menu_hnd_t *p) { //level.vote = FindVoteCmdByName("timelimit"); - //level.vote_arg = nullptr; + //level.vote_arg.clear(); //VoteCommandStore(ent); P_Menu_Close(ent); P_Menu_Open(ent, pmcallvotemenu_timelimit, -1, sizeof(pmcallvotemenu_timelimit) / sizeof(menu_t), nullptr, G_Menu_CallVote_TimeLimit_Update); @@ -544,14 +603,14 @@ void G_Menu_CallVote_ScoreLimit(gentity_t *ent, menu_hnd_t *p) { void G_Menu_CallVote_ShuffleTeams(gentity_t *ent, menu_hnd_t *p) { level.vote = FindVoteCmdByName("shuffle"); - level.vote_arg = nullptr; + level.vote_arg.clear(); VoteCommandStore(ent); P_Menu_Close(ent); } void G_Menu_CallVote_BalanceTeams(gentity_t *ent, menu_hnd_t *p) { level.vote = FindVoteCmdByName("balance"); - level.vote_arg = nullptr; + level.vote_arg.clear(); VoteCommandStore(ent); P_Menu_Close(ent); @@ -563,7 +622,7 @@ void G_Menu_CallVote_Unlagged(gentity_t *ent, menu_hnd_t *p) { void G_Menu_CallVote_Cointoss(gentity_t *ent, menu_hnd_t *p) { level.vote = FindVoteCmdByName("cointoss"); - level.vote_arg = nullptr; + level.vote_arg.clear(); VoteCommandStore(ent); P_Menu_Close(ent); } diff --git a/src/p_client.cpp b/src/p_client.cpp index 3172a18..2e01b46 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -3682,8 +3682,8 @@ bool ClientConnect(gentity_t* ent, char* userinfo, const char* social_id, bool i char newname[MAX_NETNAME]; gi.Info_ValueForKey(userinfo, "name", oldname, sizeof(oldname)); - strcpy(newname, bot_name_prefix->string); - Q_strlcat(newname, oldname, sizeof(oldname)); + Q_strlcpy(newname, bot_name_prefix->string, sizeof(newname)); + Q_strlcat(newname, oldname, sizeof(newname)); gi.Info_SetValueForKey(userinfo, "name", newname); } } From 5c56f42cbf8e34335f79e853c4bb424a07c3f551 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 21:05:51 +0000 Subject: [PATCH 014/142] Remove unused freeze implementation --- src/freeze.c | 829 ------------------------------------------------- src/g_cmds.cpp | 27 +- src/g_save.cpp | 11 +- 3 files changed, 28 insertions(+), 839 deletions(-) delete mode 100644 src/freeze.c diff --git a/src/freeze.c b/src/freeze.c deleted file mode 100644 index e23c58c..0000000 --- a/src/freeze.c +++ /dev/null @@ -1,829 +0,0 @@ -#include "g_local.h" -#include "m_player.h" -//#include "stdlog.h" -//#include "gslog.h" - -#define nteam 5 -#define game_loop for (i = 0; i < maxclients->value; i++) -#define team_loop for (i = red; i < none; i++) -#define _team_loop for (i = red; i <= none; i++) -#define map_loop for (i = 0; i < 64; i++) -#define far_off 100000000 - -#define stat_identify 18 -#define stat_red 19 -#define stat_red_arrow 23 - -#define _shotgun 0x00000001 // 1 -#define _supershotgun 0x00000002 // 2 -#define _machinegun 0x00000004 // 4 -#define _chaingun 0x00000008 // 8 -#define _grenadelauncher 0x00000010 // 16 -#define _rocketlauncher 0x00000020 // 32 -#define _hyperblaster 0x00000040 // 64 -#define _railgun 0x00000080 // 128 - -#define ready_help 0x00000001 -#define thaw_help 0x00000002 -#define frozen_help 0x00000004 -#define chase_help 0x00000008 - -#define is_motd 0x00000001 -#define end_vote 0x00000002 -#define mapnohook 0x00000004 -#define everyone_ready 0x00000008 - -cvar_t *item_respawn_time; -cvar_t *hook_max_len; -cvar_t *hook_rpf; -cvar_t *hook_min_len; -cvar_t *hook_speed; -cvar_t *point_limit; -cvar_t *new_team_count; -cvar_t *frozen_time; -cvar_t *start_weapon; -cvar_t *start_armor; -cvar_t *random_map; -cvar_t *vote_percent; -cvar_t *use_ready; -cvar_t *grapple_wall; -static int gib_queue; -static int team_max_count; -static int moan[8]; -static int lame_hack; -static float ready_time; - -qboolean playerDamage(edict_t *targ, edict_t *attacker, int damage) { - if (!targ->client) - return false; - if (meansOfDeath == MOD_TELEFRAG) - return false; - if (!attacker->client) - return false; - if (targ->client->hookstate && random() < 0.2) - targ->client->hookstate = 0; - if (targ->health > 0) { - if (!(lame_hack & everyone_ready)) { - if (!(attacker->client->resp.help & ready_help)) { - attacker->client->showscores = false; - attacker->client->resp.help |= ready_help; - gi.centerprintf(attacker, "Waiting for everyone to be ready."); - gi.sound(attacker, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_STATIC, 0); - } - return true; - } - if (targ == attacker) - return false; - if (targ->client->resp.team != attacker->client->resp.team && targ->client->respawn_time + 3 < level.time) - return false; - } else { - if (targ->client->frozen) { - if (random() < 0.1) - ThrowGib(targ, "models/objects/debris2/tris.md2", damage, GIB_ORGANIC); - return true; - } else - return false; - } - if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE) - return true; - meansOfDeath |= MOD_FRIENDLY_FIRE; - return false; -} - -qboolean freezeCheck(edict_t *ent) { - if (ent->deadflag) - return false; - if (meansOfDeath & MOD_FRIENDLY_FIRE) - return false; - switch (meansOfDeath) { - case MOD_FALLING: - case MOD_SLIME: - case MOD_LAVA: - if (random() < 0.08) - break; - case MOD_SUICIDE: - case MOD_CRUSH: - case MOD_WATER: - case MOD_EXIT: - case MOD_TRIGGER_HURT: - case MOD_BFG_LASER: - case MOD_BFG_EFFECT: - case MOD_TELEFRAG: - return false; - } - return true; -} - -void freezeAnim(edict_t *ent) { - ent->client->anim_priority = ANIM_DEATH; - if (ent->client->ps.pmove.pm_flags & PMF_DUCKED) { - if (rand() & 1) { - ent->s.frame = FRAME_crpain1 - 1; - ent->client->anim_end = FRAME_crpain1 + rand() % 4; - } else { - ent->s.frame = FRAME_crdeath1 - 1; - ent->client->anim_end = FRAME_crdeath1 + rand() % 5; - } - } else { - switch (rand() % 8) { - case 0: - ent->s.frame = FRAME_run1 - 1; - ent->client->anim_end = FRAME_run1 + rand() % 6; - break; - case 1: - ent->s.frame = FRAME_pain101 - 1; - ent->client->anim_end = FRAME_pain101 + rand() % 4; - break; - case 2: - ent->s.frame = FRAME_pain201 - 1; - ent->client->anim_end = FRAME_pain201 + rand() % 4; - break; - case 3: - ent->s.frame = FRAME_pain301 - 1; - ent->client->anim_end = FRAME_pain301 + rand() % 4; - break; - case 4: - ent->s.frame = FRAME_jump1 - 1; - ent->client->anim_end = FRAME_jump1 + rand() % 6; - break; - case 5: - ent->s.frame = FRAME_death101 - 1; - ent->client->anim_end = FRAME_death101 + rand() % 6; - break; - case 6: - ent->s.frame = FRAME_death201 - 1; - ent->client->anim_end = FRAME_death201 + rand() % 6; - break; - case 7: - ent->s.frame = FRAME_death301 - 1; - ent->client->anim_end = FRAME_death301 + rand() % 6; - break; - } - } - - if (random() < 0.2 && !IsFemale(ent)) - gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); - else - gi.sound(ent, CHAN_BODY, gi.soundindex("boss3/d_hit.wav"), 1, ATTN_NORM, 0); - ent->client->frozen = true; - ent->client->frozen_time = level.time + frozen_time->value; - ent->client->resp.thawer = NULL; - ent->client->thaw_time = far_off; - if (random() > 0.3) - ent->client->hookstate -= ent->client->hookstate & (grow_on | shrink_on); - ent->deadflag = DEAD_DEAD; - gi.linkentity(ent); -} - -qboolean gibCheck() { - if (gib_queue > 35) - return true; - else { - gib_queue++; - return false; - } -} - -void gibThink(edict_t *ent) { - gib_queue--; - G_FreeEdict(ent); -} - -static void playerView(edict_t *ent) { - int i; - edict_t *other; - vec3_t ent_origin; - vec3_t forward; - vec3_t other_origin; - vec3_t dist; - trace_t trace; - float dot; - float other_dot; - edict_t *best_other; - - if (level.framenum & 7) - return; - - other_dot = 0.3; - best_other = NULL; - VectorCopy(ent->s.origin, ent_origin); - ent_origin[2] += ent->viewheight; - AngleVectors(ent->s.angles, forward, NULL, NULL); - - game_loop - { - other = g_edicts + 1 + i; - if (!other->inuse) - continue; - if (other->client->resp.spectator) - continue; - if (other == ent) - continue; - if (other->light_level < 10) - continue; - if (other->health <= 0 && !other->client->frozen) - continue; - VectorCopy(other->s.origin, other_origin); - other_origin[2] += other->viewheight; - VectorSubtract(other_origin, ent_origin, dist); - if (VectorLength(dist) > 800) - continue; - trace = gi.trace(ent_origin, vec3_origin, vec3_origin, other_origin, ent, MASK_OPAQUE); - if (trace.fraction != 1) - continue; - VectorNormalize(dist); - dot = DotProduct(dist, forward); - if (dot > other_dot) { - other_dot = dot; - best_other = other; - } - } - if (best_other) - ent->client->viewed = best_other; - else - ent->client->viewed = NULL; -} - -static void playerThaw(edict_t *ent) { - int i; - edict_t *other; - int j; - vec3_t eorg; - - game_loop - { - other = g_edicts + 1 + i; - if (!other->inuse) - continue; - if (other->client->resp.spectator) - continue; - if (other == ent) - continue; - if (other->health <= 0) - continue; - if (other->client->resp.team != ent->client->resp.team) - continue; - for (j = 0; j < 3; j++) - eorg[j] = ent->s.origin[j] - (other->s.origin[j] + (other->mins[j] + other->maxs[j]) * 0.5); - if (VectorLength(eorg) > MELEE_DISTANCE) - continue; - if (!(other->client->resp.help & thaw_help)) { - other->client->showscores = false; - other->client->resp.help |= thaw_help; - gi.centerprintf(other, "Wait here a second to free them."); - gi.sound(other, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_STATIC, 0); - } - ent->client->resp.thawer = other; - if (ent->client->thaw_time == far_off) { - ent->client->thaw_time = level.time + 3; - gi.sound(ent, CHAN_BODY, gi.soundindex("world/steam3.wav"), 1, ATTN_NORM, 0); - } - return; - } - ent->client->resp.thawer = NULL; - ent->client->thaw_time = far_off; -} - -static void playerBreak(edict_t *ent, int force) { - int n; - - ent->client->respawn_time = level.time + 1; - if (ent->waterlevel == 3) - gi.sound(ent, CHAN_BODY, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0); - else - gi.sound(ent, CHAN_BODY, gi.soundindex("world/brkglas.wav"), 1, ATTN_NORM, 0); - n = rand() % (gib_queue > 10 ? 5 : 3); - if (rand() & 1) { - switch (n) { - case 0: - ThrowGib(ent, "models/objects/gibs/arm/tris.md2", force, GIB_ORGANIC); - break; - case 1: - ThrowGib(ent, "models/objects/gibs/bone/tris.md2", force, GIB_ORGANIC); - break; - case 2: - ThrowGib(ent, "models/objects/gibs/bone2/tris.md2", force, GIB_ORGANIC); - break; - case 3: - ThrowGib(ent, "models/objects/gibs/chest/tris.md2", force, GIB_ORGANIC); - break; - case 4: - ThrowGib(ent, "models/objects/gibs/leg/tris.md2", force, GIB_ORGANIC); - break; - } - } - while (n--) - ThrowGib(ent, "models/objects/debris1/tris.md2", force, GIB_ORGANIC); - ent->takedamage = DAMAGE_NO; - ent->movetype = MOVETYPE_TOSS; - ThrowClientHead(ent, force); - ent->client->frozen = false; - freeze[ent->client->resp.team].update = true; - ent->client->ps.stats[STAT_CHASE] = 0; -} - -static void playerUnfreeze(edict_t *ent) { - if (level.time > ent->client->frozen_time && level.time > ent->client->respawn_time) { - playerBreak(ent, 50); - return; - } - if (ent->waterlevel == 3 && !(level.framenum & 3)) - ent->client->frozen_time -= 0.15; - if (level.time > ent->client->thaw_time) { - if (!ent->client->resp.thawer || !ent->client->resp.thawer->inuse) { - ent->client->resp.thawer = NULL; - ent->client->thaw_time = far_off; - } else { - ent->client->resp.thawer->client->resp.score++; - ent->client->resp.thawer->client->resp.thawed++; - sl_LogScore(&gi, ent->client->resp.thawer->client->pers.netname, NULL, "Thaw", NULL, 1, level.time, ent->client->resp.thawer->client->ping); - freeze[ent->client->resp.team].thawed++; - if (rand() & 1) - gi.bprintf(PRINT_HIGH, "%s thaws %s like a package of frozen peas.\n", ent->client->resp.thawer->client->pers.netname, ent->client->pers.netname); - else - gi.bprintf(PRINT_HIGH, "%s evicts %s from their igloo.\n", ent->client->resp.thawer->client->pers.netname, ent->client->pers.netname); - playerBreak(ent, 100); - } - } -} - -static void playerMove(edict_t *ent) { - int i; - edict_t *other; - vec3_t forward; - float dist; - int j; - vec3_t eorg; - - if (ent->client->hookstate) - return; - AngleVectors(ent->s.angles, forward, NULL, NULL); - game_loop - { - other = g_edicts + 1 + i; - if (!other->inuse) - continue; - if (other->client->resp.spectator) - continue; - if (other == ent) - continue; - if (!other->client->frozen) - continue; - if (other->client->resp.team == ent->client->resp.team) - continue; - if (other->client->hookstate) - continue; - for (j = 0; j < 3; j++) - eorg[j] = ent->s.origin[j] - (other->s.origin[j] + (other->mins[j] + other->maxs[j]) * 0.5); - dist = VectorLength(eorg); - if (dist > MELEE_DISTANCE) - continue; - VectorScale(forward, 600, other->velocity); - other->velocity[2] = 200; - gi.linkentity(other); - } -} - -void freezeMain(edict_t *ent) { - if (!ent->inuse) - return; - playerView(ent); - if (ent->client->resp.spectator) - return; - if (ent->client->frozen) { - playerThaw(ent); - playerUnfreeze(ent); - } else if (ent->health > 0) - playerMove(ent); -} - -void freezeScore(edict_t *ent, edict_t *killer) { - int i, j, k; - edict_t *other; - int team, score; - int total[nteam]; - int sorted[nteam][MAX_CLIENTS]; - int sortedscores[nteam][MAX_CLIENTS]; - int count, best_total, best_team; - int x, y; - int move_over; - char string[1400]; - int stringlength; - char *tag; - char entry[1024]; - gclient_t *cl; - - _team_loop - total[i] = 0; - game_loop - { - other = g_edicts + 1 + i; - if (!other->inuse) - continue; - if (other->client->resp.spectator) - team = none; - else - team = other->client->resp.team; - score = other->client->resp.score; - for (j = 0; j < total[team]; j++) { - if (score > sortedscores[team][j]) - break; - } - for (k = total[team]; k > j; k--) { - sorted[team][k] = sorted[team][k - 1]; - sortedscores[team][k] = sortedscores[team][k - 1]; - } - sorted[team][j] = i; - sortedscores[team][j] = score; - total[team]++; - } - - for (;;) { - count = 0; - team_loop - count += 2 + total[i]; - if (count <= 48) - break; - best_total = 0; - team_loop - if (total[i] > best_total) { - best_total = total[i]; - best_team = i; - } - if (best_total) - total[best_team]--; - } - - x = 0; - y = 32; - - count = 4; - _team_loop - if (total[i]) - count += 3 + total[i]; - move_over = (int)(count / 2) * 8; - - string[0] = 0; - stringlength = strlen(string); - - _team_loop - { - if (i == red) - tag = "k_redkey"; - else if (i == blue) - tag = "k_bluekey"; - else if (i == green) - tag = "k_security"; - else - tag = "k_powercube"; - - if (i == none) - Com_sprintf(entry, sizeof(entry), "xv %d yv %d string \"%6.6s\" ", x, y, freeze_team_[i]); - else - Com_sprintf(entry, sizeof(entry), "xv %d yv %d if %d picn %s endif string \"%6.6s Sco%3d Tha%3d\" ", x, y, 19 + i, tag, freeze_team_[i], freeze[i].score, freeze[i].thawed); - k = strlen(entry); - if (stringlength + k > 1024) - break; - if (total[i]) { - strcpy(string + stringlength, entry); - stringlength += k; - y += 16; - } else - continue; - for (j = 0; j < total[i]; j++) { - if (y >= 224) { - if (x == 0) - x = 160; - else - break; - y = 32; - } - cl = &game.clients[sorted[i][j]]; - Com_sprintf(entry, sizeof(entry), "ctf %d %d %d %d %d ", x, y, sorted[i][j], cl->resp.score, level.intermissiontime ? cl->resp.thawed : (cl->ping > 999 ? 999 : cl->ping)); - if (cl->frozen) - sprintf(entry + strlen(entry), "xv %d yv %d string2 \"/\" ", x + 56, y); - k = strlen(entry); - if (stringlength + k > 1024) - break; - strcpy(string + stringlength, entry); - stringlength += k; - y += 8; - } - Com_sprintf(entry, sizeof(entry), "xv %d yv %d string \"--------------------\" ", x, y); - k = strlen(entry); - if (stringlength + k > 1024) - break; - strcpy(string + stringlength, entry); - stringlength += k; - if (y >= 208 || (y >= move_over && x == 0)) { - if (x == 0) - x = 160; - else - break; - y = 32; - } else - y += 8; - } - - gi.WriteByte(svc_layout); - gi.WriteString(string); -} - -void freezeIntermission(void) { - int i, j, k; - int team; - - i = j = k = 0; - team_loop - if (freeze[i].score > j) - j = freeze[i].score; - - team_loop - if (freeze[i].score == j) { - k++; - team = i; - } - - if (k > 1) { - i = j = k = 0; - team_loop - if (freeze[i].thawed > j) - j = freeze[i].thawed; - - team_loop - if (freeze[i].thawed == j) { - k++; - team = i; - } - } - if (k != 1) { - gi.bprintf(PRINT_HIGH, "Stalemate!\n"); - return; - } - gi.bprintf(PRINT_HIGH, "%s team is the winner!\n", freeze_team[team]); - team_loop - freeze[i].win_time = level.time; - freeze[team].win_time = far_off; -} - -char *makeGreen(char *s) { - static char string[16]; - int i; - - if (!*s) - return ""; - for (i = 0; i < 15 && *s; i++, s++) { - string[i] = *s; - string[i] |= 0x80; - } - string[i] = 0; - return string; -} - -static void playerHealth(edict_t *ent) { - int n; - - for (n = 0; n < game.num_items; n++) - ent->client->pers.inventory[n] = 0; - - ent->client->quad_framenum = 0; - ent->client->invincible_framenum = 0; - ent->flags &= ~FL_POWER_ARMOR; - - ent->health = ent->client->pers.max_health; - - ent->s.sound = 0; - ent->client->weapon_sound = 0; -} - -static void breakTeam(int team) { - int i; - edict_t *ent; - float break_time; - - break_time = level.time; - game_loop - { - ent = g_edicts + 1 + i; - if (!ent->inuse) - continue; - if (ent->client->frozen) { - if (ent->client->resp.team != team && team_max_count >= 3) - continue; - ent->client->frozen_time = break_time; - break_time += 0.25; - continue; - } - if (ent->health > 0 && team_max_count < 3) { - playerHealth(ent); - playerWeapon(ent); - } - } - freeze[team].break_time = break_time + 1; - if (rand() & 1) - gi.bprintf(PRINT_HIGH, "%s team was run circles around by their foe.\n", freeze_team[team]); - else - gi.bprintf(PRINT_HIGH, "%s team was less than a match for their foe.\n", freeze_team[team]); -} - -static void updateTeam(int team) { - int i; - edict_t *ent; - int frozen, alive; - char small[32]; - int play_sound = 0; - - frozen = alive = 0; - game_loop - { - ent = g_edicts + 1 + i; - if (!ent->inuse) - continue; - if (ent->client->resp.spectator) - continue; - if (ent->client->resp.team != team) - continue; - if (ent->client->frozen) - frozen++; - if (ent->health > 0) - alive++; - } - freeze[team].frozen = frozen; - freeze[team].alive = alive; - - if (frozen && !alive) { - team_loop - { - if (freeze[i].alive) { - play_sound++; - freeze[i].score++; - freeze[i].win_time = level.time + 5; - freeze[i].update = true; - } - } - breakTeam(team); - - if (play_sound <= 1) - gi.positioned_sound(vec3_origin, world, CHAN_VOICE | CHAN_RELIABLE, gi.soundindex("world/xian1.wav"), 1, ATTN_NONE, 0); - } - - Com_sprintf(small, sizeof(small), " %s%3d/%3d", freeze_team__[team], freeze[team].score, freeze[team].alive); - // if (!(freeze[team].alive == 1 && freeze[team].frozen)) - // makeGreen(small); - gi.configstring(CS_GENERAL + team, small); -} - -qboolean endCheck() { - int i; - - if (!(level.framenum & 31)) { - if (new_team_count->value) { - int _new_team_count = new_team_count->value; - int total[nteam]; - - _team_loop - total[i] = freeze[i].alive + freeze[i].frozen; - - if (total[yellow]) - team_max_count = 4; - else if (total[red] >= _new_team_count && total[blue] >= _new_team_count) { - if (total[green] >= _new_team_count) - team_max_count = 4; - else - team_max_count = 3; - } else if (total[green]) - team_max_count = 3; - else - team_max_count = 0; - } else - team_max_count = 0; - } - - if (use_ready->value && !(lame_hack & everyone_ready)) { - switch ((int)(ready_time / FRAMETIME) - level.framenum) { - case 150: - case 100: - case 50: - case 40: - case 30: - case 20: - gi.bprintf(PRINT_HIGH, "Begin in %d seconds!\n", (int)(((ready_time / FRAMETIME) - level.framenum) * FRAMETIME)); - } - if (level.time > ready_time) { - edict_t *ent; - - lame_hack |= everyone_ready; - gi.bprintf(PRINT_HIGH, "Begin!\n"); - game_loop - { - ent = g_edicts + 1 + i; - if (!ent->inuse) - continue; - if (ent->client->resp.spectator) - continue; - if (ent->health > 0) { - playerHealth(ent); - playerWeapon(ent); - } - } - } - } else - lame_hack |= everyone_ready; - - team_loop - if (freeze[i].update && level.time > freeze[i].last_update) { - updateTeam(i); - freeze[i].update = false; - freeze[i].last_update = level.time + 3; - } - - if (point_limit->value) { - int _point_limit; - - _point_limit = point_limit->value; - if (team_max_count >= 3) - _point_limit *= 3; - team_loop - if (freeze[i].score >= _point_limit) - return true; - } - if (lame_hack & end_vote) - return true; - - return false; -} - -void freezeRespawn(edict_t *ent, float delay) { - if (item_respawn_time->value) - SetRespawn(ent, item_respawn_time->value); - else - SetRespawn(ent, delay); -} - -void playerShell(edict_t *ent, int team) { - ent->s.effects |= EF_COLOR_SHELL; - ent->s.renderfx |= RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE; - -} - -void freezeEffects(edict_t *ent) { - if (level.intermissiontime) - return; - if (!ent->client->frozen) - return; - if (!ent->client->resp.thawer || level.framenum & 8) - playerShell(ent, ent->client->resp.team); -} - -void playerStat(edict_t *ent) { - int i; - - if (ent->client->viewed && ent->client->viewed->inuse) { - int playernum = ent->client->viewed - g_edicts - 1; - - ent->client->ps.stats[stat_identify] = CS_PLAYERSKINS + playernum; - } else - ent->client->ps.stats[stat_identify] = 0; - - team_loop - { - if (((i == green && team_max_count < 3) || (i == yellow && team_max_count < 4)) || - (freeze[i].win_time > level.time && !(level.framenum & 8))) { - ent->client->ps.stats[stat_red + i] = 0; - ent->client->ps.stats[stat_red_arrow + i] = 0; - continue; - } - - ent->client->ps.stats[stat_red + i] = CS_GENERAL + i; - if (ent->client->resp.team == i && !ent->client->resp.spectator) - ent->client->ps.stats[stat_red_arrow + i] = CS_GENERAL + 5; - else - ent->client->ps.stats[stat_red_arrow + i] = 0; - } -} - -void freezeSpawn() { - int i; - - loadMessage(); - loadMap(); - - memset(freeze, 0, sizeof(freeze)); - team_loop - freeze[i].update = true; - lame_hack &= ~everyone_ready; - ready_time = far_off; - gib_queue = 0; - - moan[0] = gi.soundindex("insane/insane1.wav"); - moan[1] = gi.soundindex("insane/insane2.wav"); - moan[2] = gi.soundindex("insane/insane3.wav"); - moan[3] = gi.soundindex("insane/insane4.wav"); - moan[4] = gi.soundindex("insane/insane6.wav"); - moan[5] = gi.soundindex("insane/insane8.wav"); - moan[6] = gi.soundindex("insane/insane9.wav"); - moan[7] = gi.soundindex("insane/insane10.wav"); - - mapLight(); - gi.configstring(CS_GENERAL + 5, ">"); -} diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 34a5451..54ffdb6 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -2702,7 +2702,8 @@ static bool ValidVoteCommand(gentity_t *ent) { return false; level.vote = cc; - level.vote_arg = std::string(gi.argv(2)); + const char *raw_arg = gi.argc() > 2 ? gi.argv(2) : ""; + level.vote_arg = std::string(raw_arg); //gi.Com_PrintFmt("argv={} vote_arg={}\n", gi.argv(2), level.vote_arg); return true; } @@ -2765,11 +2766,15 @@ static void Cmd_CallVote_f(gentity_t *ent) { for (size_t i = 0; i < ARRAY_LEN(vote_cmds); i++, cc++) { if (!cc->name) continue; - + if (g_vote_flags->integer & cc->flag) continue; - - strcat(vstr, G_Fmt("{} ", cc->name).data()); + + std::string option = std::string(G_Fmt("{} ", cc->name)); + if (Q_strlcat(vstr, option.c_str(), sizeof(vstr)) >= sizeof(vstr)) { + vstr[sizeof(vstr) - 1] = '\0'; + break; + } } if (!g_allow_voting->integer || strlen(vstr) <= 1) { @@ -2900,7 +2905,7 @@ static void Cmd_Follow_f(gentity_t *ent) { return; } - if (ClientIsPlaying(follow_ent->client)) { + if (!ClientIsPlaying(follow_ent->client)) { gi.Client_Print(ent, PRINT_HIGH, "Specified client is not playing.\n"); return; } @@ -2926,8 +2931,18 @@ Cmd_FollowLeader_f ================= */ static void Cmd_FollowLeader_f(gentity_t *ent) { - gentity_t *leader = &g_entities[level.sorted_clients[0] + 1]; ent->client->sess.pc.follow_leader ^= true; + + if (ent->client->sess.pc.follow_leader) { + if (!level.num_playing_clients || level.sorted_clients[0] < 0) { + ent->client->sess.pc.follow_leader = false; + gi.Client_Print(ent, PRINT_HIGH, "No leader available to follow.\n"); + gi.LocClient_Print(ent, PRINT_HIGH, "Auto-follow leader: OFF\n"); + return; + } + } + + gentity_t *leader = &g_entities[level.sorted_clients[0] + 1]; gi.LocClient_Print(ent, PRINT_HIGH, "Auto-follow leader: {}\n", ent->client->sess.pc.follow_leader ? "ON" : "OFF"); if (!ClientIsPlaying(ent->client) && ent->client->sess.pc.follow_leader && ent->client->follow_target != leader) { diff --git a/src/g_save.cpp b/src/g_save.cpp index 268212c..8515559 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -1454,8 +1454,9 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t json_print_error(field, "static-length dynamic string overrun", false); else { size_t len = strlen(json.asCString()); - char *str = *((char **)data) = (char *)gi.TagMalloc(type->count ? type->count : (len + 1), type->tag); - strcpy(str, json.asCString()); + size_t alloc_size = type->count ? type->count : (len + 1); + char *str = *((char **)data) = (char *)gi.TagMalloc(alloc_size, type->tag); + Q_strlcpy(str, json.asCString(), alloc_size); str[len] = 0; } } else if (json.isArray()) { @@ -1485,8 +1486,10 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t if (json.isString()) { if (type->count && strlen(json.asCString()) >= type->count) json_print_error(field, "fixed length string overrun", false); - else - strcpy((char *)data, json.asCString()); + else { + size_t dest_size = type->count ? type->count : (strlen(json.asCString()) + 1); + Q_strlcpy((char *)data, json.asCString(), dest_size); + } } else if (json.isArray()) { if (type->count && json.size() >= type->count - 1) json_print_error(field, "fixed length string overrun", false); From 13067b4499464bdb26d58c80772b3d6ba28293c6 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 21:22:49 +0000 Subject: [PATCH 015/142] Fix broadcast filters and stabilize string helpers --- src/cg_screen.cpp | 13 +++++-- src/g_utils.cpp | 98 ++++++++++++++++++++++++++++++++++++----------- src/p_client.cpp | 68 +++++++++++++++++++++----------- 3 files changed, 130 insertions(+), 49 deletions(-) diff --git a/src/cg_screen.cpp b/src/cg_screen.cpp index 5657789..b3445ba 100644 --- a/src/cg_screen.cpp +++ b/src/cg_screen.cpp @@ -694,11 +694,14 @@ static void CG_DrawTable(int x, int y, uint32_t width, uint32_t height, int32_t } /* -================= +============= CG_TimeStringMs -================= + +Format a client-visible timer string with millisecond precision. +============= */ static const char *CG_TimeStringMs(const int msec) { + static char buffer[32]; int hours, mins, seconds, ms = msec; seconds = ms / 1000; @@ -709,10 +712,12 @@ static const char *CG_TimeStringMs(const int msec) { mins -= hours * 60; if (hours > 0) { - return G_Fmt("{}:{:02}:{:02}.{}", hours, mins, seconds, ms).data(); + G_FmtTo(buffer, "{}:{:02}:{:02}.{}", hours, mins, seconds, ms); } else { - return G_Fmt("{:02}:{:02}.{}", mins, seconds, ms).data(); + G_FmtTo(buffer, "{:02}:{:02}.{}", mins, seconds, ms); } + + return buffer; } /* diff --git a/src/g_utils.cpp b/src/g_utils.cpp index c90ce92..88c3736 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -3,6 +3,7 @@ // g_utils.c -- misc utility functions for game module #include "g_local.h" +#include /* ============= @@ -129,16 +130,41 @@ void G_PrintActivationMessage(gentity_t *ent, gentity_t *activator, bool coop_gl } } +/* +============= +BroadcastFriendlyMessage + +Broadcast a friendly message to active teammates or, in non-team modes, all +active players. +============= +*/ void BroadcastFriendlyMessage(team_t team, const char *msg) { for (auto ce : active_clients()) { - if (!ClientIsPlaying(ce->client) || (Teams() && ce->client->sess.team == team)) { - gi.LocClient_Print(ce, PRINT_HIGH, G_Fmt("{}{}", ce->client->sess.team != TEAM_SPECTATOR ? "[TEAM]: " : "", msg).data()); + const bool playing = ClientIsPlaying(ce->client); + if (!playing) { + if (!Teams()) + continue; + gentity_t *follow = ce->client->follow_target; + if (!follow || !follow->client || follow->client->sess.team != team) + continue; + } else if (Teams() && ce->client->sess.team != team) { + continue; } + gi.LocClient_Print(ce, PRINT_HIGH, G_Fmt("{}{}", playing && ce->client->sess.team != TEAM_SPECTATOR ? "[TEAM]: " : "", msg).data()); } } +/* +============= +BroadcastTeamMessage + +Broadcast a message to all clients actively playing for the specified team. +============= +*/ void BroadcastTeamMessage(team_t team, print_type_t level, const char *msg) { for (auto ce : active_clients()) { + if (!ClientIsPlaying(ce->client)) + continue; if (ce->client->sess.team != team) continue; @@ -817,11 +843,14 @@ bool Teams() { } /* -================= +============= G_TimeString -================= + +Format a match timer string with minute precision. +============= */ const char *G_TimeString(const int msec, bool state) { + static char buffer[32]; if (state) { if (level.match_state < matchst_t::MATCH_COUNTDOWN) return "WARMUP"; @@ -840,17 +869,22 @@ const char *G_TimeString(const int msec, bool state) { mins -= hours * 60; if (hours > 0) { - return G_Fmt("{}{}:{:02}:{:02}", msec < 1000 ? "-" : "", hours, mins, seconds).data(); + G_FmtTo(buffer, "{}{}:{:02}:{:02}", msec < 1000 ? "-" : "", hours, mins, seconds); } else { - return G_Fmt("{}{:02}:{:02}", msec < 1000 ? "-" : "", mins, seconds).data(); + G_FmtTo(buffer, "{}{:02}:{:02}", msec < 1000 ? "-" : "", mins, seconds); } + + return buffer; } /* -================= +============= G_TimeStringMs -================= + +Format a match timer string with millisecond precision. +============= */ const char *G_TimeStringMs(const int msec, bool state) { + static char buffer[32]; if (state) { if (level.match_state < matchst_t::MATCH_COUNTDOWN) return "WARMUP"; @@ -869,10 +903,12 @@ const char *G_TimeStringMs(const int msec, bool state) { mins -= hours * 60; if (hours > 0) { - return G_Fmt("{}:{:02}:{:02}.{}", hours, mins, seconds, ms).data(); + G_FmtTo(buffer, "{}:{:02}:{:02}.{}", hours, mins, seconds, ms); } else { - return G_Fmt("{:02}:{:02}.{}", mins, seconds, ms).data(); + G_FmtTo(buffer, "{:02}:{:02}.{}", mins, seconds, ms); } + + return buffer; } team_t StringToTeamNum(const char *in) { @@ -986,22 +1022,26 @@ bool InCoopStyle() { } /* -================= +============= ClientEntFromString -================= + +Resolve a client entity from a name or validated numeric identifier string. +============= */ gentity_t *ClientEntFromString(const char *in) { - // check by nick first for (auto ec : active_clients()) if (!strcmp(in, ec->client->resp.netname)) return ec; - // otherwise check client num - uint32_t num = strtoul(in, nullptr, 10); - if (num >= 0 && num < game.maxclients) - return &g_entities[&game.clients[num] - game.clients + 1]; + char *end = nullptr; + errno = 0; + const unsigned long num = strtoul(in, &end, 10); + if (errno == ERANGE || !end || *end != '\0') + return nullptr; + if (num >= static_cast(game.maxclients)) + return nullptr; - return nullptr; + return &g_entities[&game.clients[num] - game.clients + 1]; } /* @@ -1082,19 +1122,31 @@ void MS_Set(gclient_t *cl, mstats_t index, int value) { cl->resp.mstats[index] = value; } +/* +============= +stime + +Return a stable timestamp string for file naming. +============= +*/ const char *stime() { struct tm *ltime; time_t gmtime; + static char buffer[32]; time(&gmtime); ltime = localtime(&gmtime); - const char *s; - s = G_Fmt("{}{:02}{:02}{:02}{:02}{:02}", - 1900 + ltime->tm_year, ltime->tm_mon + 1, ltime->tm_mday, ltime->tm_hour, ltime->tm_min, ltime->tm_sec - ).data(); + if (!ltime) { + buffer[0] = '\0'; + return buffer; + } + + G_FmtTo(buffer, "{}{:02}{:02}{:02}{:02}{:02}", + 1900 + ltime->tm_year, ltime->tm_mon + 1, ltime->tm_mday, + ltime->tm_hour, ltime->tm_min, ltime->tm_sec); - return s; + return buffer; } void AnnouncerSound(gentity_t *ent, const char *announcer_sound, const char *backup_sound, bool use_backup) { diff --git a/src/p_client.cpp b/src/p_client.cpp index 2e01b46..74f5e2f 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -167,13 +167,19 @@ p_mods_skins_t original_models[MAX_PLAYER_STOCK_MODELS] = { } }; -static const char* ClientSkinOverride(const char* s) { +/* +============= +ClientSkinOverride +Return an allowed skin path, falling back to stock defaults when needed. +============= +*/ +static const char* ClientSkinOverride(const char* s) { if (g_allow_custom_skins->integer) { - //gi.Com_PrintFmt("{}: returning {}\n", __FUNCTION__, s); return s; } + static char skin_buffer[MAX_QPATH]; size_t i; std::string pm(s); std::string ps(s); @@ -188,26 +194,23 @@ static const char* ClientSkinOverride(const char* s) { ps = "grunt"; } - // check stock model list for (i = 0; i < MAX_PLAYER_STOCK_MODELS; i++) { if (pm == original_models[i].mname) { - // found the model, now check stock skin list - for (size_t j = 0; j < MAX_PLAYER_STOCK_SKINS; j++) + for (size_t j = 0; j < MAX_PLAYER_STOCK_SKINS; j++) { if (ps == original_models[i].sname[j]) { - //return G_Fmt("{}/{}", pm, ps).data(); - // found the skin, no change in player skin return s; } + } - // didn't find the skin but found the model, return model default skin gi.Com_PrintFmt("{}: reverting to default skin for model: {} -> {}\n", __FUNCTION__, s, original_models[i].mname, original_models[i].sname[0]); - return G_Fmt("{}/{}", original_models[i].mname, original_models[i].sname[0]).data(); + G_FmtTo(skin_buffer, "{}/{}", original_models[i].mname, original_models[i].sname[0]); + return skin_buffer; } } - //gi.Com_PrintFmt("{}: returning {}\n", __FUNCTION__, s); gi.Com_PrintFmt("{}: reverting to default model: {} -> male/grunt\n", __FUNCTION__, s); - return "male/grunt"; + Q_strlcpy(skin_buffer, "male/grunt", sizeof(skin_buffer)); + return skin_buffer; } //======================================================================= @@ -239,19 +242,26 @@ static void PCfg_WriteConfig(gentity_t *ent) { gi.Com_PrintFmt("Player config written to: \"{}\"\n", name); } */ +/* +============= +PCfg_ClientInitPConfig + +Load or create the player's configuration file on connect. +============= +*/ static void PCfg_ClientInitPConfig(gentity_t* ent) { - bool file_exists = false; - bool cfg_valid = true; + bool file_exists = false; + bool cfg_valid = true; if (!ent->client) return; if (ent->svflags & SVF_BOT) return; - // load up file - const char* name = G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id).data(); + const std::string path = std::string(G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id)); + const char *name = path.c_str(); FILE* f = fopen(name, "rb"); + char* buffer = nullptr; if (f != NULL) { - char* buffer = nullptr; size_t length; size_t read_length; @@ -276,18 +286,25 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { fclose(f); if (!cfg_valid) { + if (buffer) { + gi.TagFree(buffer); + } gi.Com_PrintFmt("{}: Player config load error for \"{}\", discarding.\n", __FUNCTION__, name); return; } + + if (buffer) { + gi.TagFree(buffer); + buffer = nullptr; + } } - // save file if it doesn't exist if (!file_exists) { f = fopen(name, "w"); if (f) { - const char* str = G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname).data(); + const std::string header = std::string(G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname)); - fwrite(str, 1, strlen(str), f); + fwrite(header.c_str(), 1, header.length(), f); gi.Com_PrintFmt("{}: Player config written to: \"{}\"\n", __FUNCTION__, name); fclose(f); } @@ -295,9 +312,6 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { gi.Com_PrintFmt("{}: Cannot save player config: {}\n", __FUNCTION__, name); } } - else { - //gi.Com_PrintFmt("{}: Player config not saved as file already exists: \"{}\"\n", __FUNCTION__, name); - } } //======================================================================= @@ -350,11 +364,21 @@ mon_name_t monname[] = { { "monster_widow2", "Black Widow" }, }; +/* +============= +MonsterName + +Look up a friendly monster name, falling back to the classname when not found. +============= +*/ static const char* MonsterName(const char* classname) { + if (!classname) + return nullptr; for (size_t i = 0; i < ARRAY_LEN(monname); i++) { if (!Q_strncasecmp(classname, monname[i].classname, strlen(classname))) return monname[i].longname; } + return classname; } static bool IsVowel(const char c) { From 6db605b743046b9484963f9c97d24bca03ebd3c0 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 21:31:14 +0000 Subject: [PATCH 016/142] plasmabeam knockback fix for muffmode ruleset --- src/g_local.h | 2 +- src/p_weapon.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index aa0dcfa..b210cba 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -10,7 +10,7 @@ constexpr const char *GAMEVERSION = "baseq2"; constexpr const char *GAMEMOD_TITLE = "Muff Mode BETA"; -constexpr const char *GAMEMOD_VERSION = "0.19.50.4"; +constexpr const char *GAMEMOD_VERSION = "0.21.00"; //================================================================== diff --git a/src/p_weapon.cpp b/src/p_weapon.cpp index df069cd..3c046b3 100644 --- a/src/p_weapon.cpp +++ b/src/p_weapon.cpp @@ -2584,7 +2584,7 @@ static void Weapon_PlasmaBeam_Fire(gentity_t *ent) { switch (game.ruleset) { case RS_MM: damage = deathmatch->integer ? 10 : 15; - kick = deathmatch->integer ? 50 : 30; + kick = damage; break; case RS_Q3A: damage = deathmatch->integer ? 8 : 15; From 24061a36b87602e90e240e5d24823fa3a6fcd404 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 11 Nov 2025 21:40:05 +0000 Subject: [PATCH 017/142] Make fmt header-only in release --- src/game.vcxproj | 2 +- src/q_std.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/game.vcxproj b/src/game.vcxproj index 5edd140..e3f3efa 100644 --- a/src/game.vcxproj +++ b/src/game.vcxproj @@ -89,7 +89,7 @@ true true true - KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;FMT_HEADER_ONLY;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true stdcpp17 4267;4244 diff --git a/src/q_std.h b/src/q_std.h index a2e5215..8024c23 100644 --- a/src/q_std.h +++ b/src/q_std.h @@ -33,6 +33,9 @@ namespace fmt = std; #define FMT_STRING(s) s #else +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY 1 +#endif #include #endif From f9e610ff93eadd37a1ce7e745e588922e86fd9a7 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 13 Nov 2025 16:58:40 +0000 Subject: [PATCH 018/142] Harden ExitLevel intermission screenshot handling --- src/g_main.cpp | 71 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index 5ebcb1f..0a2c2f7 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -3430,33 +3430,62 @@ ExitLevel */ void ExitLevel() { if (deathmatch->integer && g_dm_intermission_shots->integer && level.num_playing_human_clients > 0) { - struct tm *ltime; - time_t gmtime; + time_t current_time; + struct tm local_time {}; + bool have_time = time(¤t_time) != static_cast(-1); - time(&gmtime); - ltime = localtime(&gmtime); - time(&gmtime); - ltime = localtime(&gmtime); + if (have_time) { +#if defined(_WIN32) + have_time = localtime_s(&local_time, ¤t_time) == 0; +#else + have_time = localtime_r(¤t_time, &local_time) != nullptr; +#endif + } - const char *s = ""; + if (!have_time) { + gi.Com_Print("Failed to resolve local time for intermission screenshot.\n"); + } else { + std::string screenshot_command; + + const auto first_index = level.sorted_clients[0]; + const auto second_index = level.sorted_clients[1]; + const bool have_first = first_index >= 0 && first_index < static_cast(MAX_CLIENTS); + const bool have_second = second_index >= 0 && second_index < static_cast(MAX_CLIENTS); + + if (GT(GT_DUEL) && level.num_playing_human_clients > 1 && have_first && have_second) { + gentity_t *e1 = &g_entities[first_index + 1]; + gentity_t *e2 = &g_entities[second_index + 1]; + const char *n1 = (e1 && e1->client) ? e1->client->resp.netname : ""; + const char *n2 = (e2 && e2->client) ? e2->client->resp.netname : ""; + + screenshot_command = std::string(G_Fmt("screenshot {}-vs-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02\n", + n1, n2, level.mapname, 1900 + local_time.tm_year, local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec)); + gi.Com_Print(screenshot_command.c_str()); + } else if (have_first) { + gentity_t *ent = &g_entities[first_index + 1]; + const bool has_follow_target = ent && ent->client && ent->client->follow_target && ent->client->follow_target->client; + const char *name = has_follow_target ? ent->client->follow_target->client->resp.netname : + (ent && ent->client ? ent->client->resp.netname : ""); + + screenshot_command = std::string(G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02\n", gt_short_name_upper[g_gametype->integer], + name, level.mapname, 1900 + local_time.tm_year, local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec)); + } else { + for (auto player : active_clients()) { + if (!player->client || !ClientIsPlaying(player->client)) + continue; - if (GT(GT_DUEL)) { - gentity_t *e1 = &g_entities[level.sorted_clients[0] + 1]; - gentity_t *e2 = &g_entities[level.sorted_clients[1] + 1]; - const char *n1 = e1 ? e1->client->resp.netname : ""; - const char *n2 = e2 ? e2->client->resp.netname : ""; + const bool has_follow_target = player->client->follow_target && player->client->follow_target->client; + const char *name = has_follow_target ? player->client->follow_target->client->resp.netname : player->client->resp.netname; - s = G_Fmt("screenshot {}-vs-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02}\n", - n1, n2, level.mapname, 1900 + ltime->tm_year, ltime->tm_mon + 1, ltime->tm_mday, ltime->tm_hour, ltime->tm_min, ltime->tm_sec).data(); - gi.Com_Print(s); - } else { - gentity_t *ent = &g_entities[1]; - const char *name = ent->client->follow_target ? ent->client->follow_target->client->resp.netname : ent->client->resp.netname; + screenshot_command = std::string(G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02\n", gt_short_name_upper[g_gametype->integer], + name, level.mapname, 1900 + local_time.tm_year, local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec)); + break; + } + } - s = G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02}\n", gt_short_name_upper[g_gametype->integer], - name, level.mapname, 1900 + ltime->tm_year, ltime->tm_mon + 1, ltime->tm_mday, ltime->tm_hour, ltime->tm_min, ltime->tm_sec).data(); + if (!screenshot_command.empty()) + gi.AddCommandString(screenshot_command.c_str()); } - gi.AddCommandString(s); } // [Paril-KEX] N64 fade From 02e556a1f6f82b9e69e88d63128089688c1c61b7 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 13 Nov 2025 20:08:26 +0000 Subject: [PATCH 019/142] Fix map list shuffle format usage --- src/g_main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index 0a2c2f7..6bc65b9 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -2994,7 +2994,8 @@ void Match_End() { if (values[0] == level.mapname) std::swap(values[0], values[values.size() - 1]); - gi.cvar_forceset("g_map_list", fmt::format("{}", join_strings(values, " ")).data()); + auto shuffled_map_list = join_strings(values, " "); + gi.cvar_forceset("g_map_list", shuffled_map_list.c_str()); BeginIntermission(CreateTargetChangeLevel(values[0].c_str())); return; From c8b4115ce72f2c01a1ccaba6e5977103925712bd Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 13 Nov 2025 20:16:40 +0000 Subject: [PATCH 020/142] Fix screenshot command format strings --- src/g_main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index 6bc65b9..e5f9e40 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -3459,7 +3459,7 @@ void ExitLevel() { const char *n1 = (e1 && e1->client) ? e1->client->resp.netname : ""; const char *n2 = (e2 && e2->client) ? e2->client->resp.netname : ""; - screenshot_command = std::string(G_Fmt("screenshot {}-vs-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02\n", + screenshot_command = std::string(G_Fmt("screenshot {}-vs-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02}\n", n1, n2, level.mapname, 1900 + local_time.tm_year, local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec)); gi.Com_Print(screenshot_command.c_str()); } else if (have_first) { @@ -3468,7 +3468,7 @@ void ExitLevel() { const char *name = has_follow_target ? ent->client->follow_target->client->resp.netname : (ent && ent->client ? ent->client->resp.netname : ""); - screenshot_command = std::string(G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02\n", gt_short_name_upper[g_gametype->integer], + screenshot_command = std::string(G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02}\n", gt_short_name_upper[g_gametype->integer], name, level.mapname, 1900 + local_time.tm_year, local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec)); } else { for (auto player : active_clients()) { @@ -3478,7 +3478,7 @@ void ExitLevel() { const bool has_follow_target = player->client->follow_target && player->client->follow_target->client; const char *name = has_follow_target ? player->client->follow_target->client->resp.netname : player->client->resp.netname; - screenshot_command = std::string(G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02\n", gt_short_name_upper[g_gametype->integer], + screenshot_command = std::string(G_Fmt("screenshot {}-{}-{}-{}_{:02}_{:02}-{:02}_{:02}_{:02}\n", gt_short_name_upper[g_gametype->integer], name, level.mapname, 1900 + local_time.tm_year, local_time.tm_mon + 1, local_time.tm_mday, local_time.tm_hour, local_time.tm_min, local_time.tm_sec)); break; } From f93ddf6ce2a69519f7a928b5b8adb26d1c6f0839 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 13 Nov 2025 20:27:06 +0000 Subject: [PATCH 021/142] couple safety checks, version to 0.21.01 --- src/g_combat.cpp | 2 +- src/g_local.h | 2 +- src/game.vcxproj | 13 ++++--------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/g_combat.cpp b/src/g_combat.cpp index bb16b06..aec749a 100644 --- a/src/g_combat.cpp +++ b/src/g_combat.cpp @@ -717,7 +717,7 @@ void T_Damage(gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, const } } - if (targ != attacker && attacker->client && targ->health > 0 && !targ->client->sess.is_banned) { + if (targ != attacker && targ->client && attacker->client && targ->health > 0 && !targ->client->sess.is_banned) { int stat_take = take; if (stat_take > targ->health) stat_take = targ->health; diff --git a/src/g_local.h b/src/g_local.h index b210cba..211be5f 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -10,7 +10,7 @@ constexpr const char *GAMEVERSION = "baseq2"; constexpr const char *GAMEMOD_TITLE = "Muff Mode BETA"; -constexpr const char *GAMEMOD_VERSION = "0.21.00"; +constexpr const char *GAMEMOD_VERSION = "0.21.01"; //================================================================== diff --git a/src/game.vcxproj b/src/game.vcxproj index e3f3efa..e7434c6 100644 --- a/src/game.vcxproj +++ b/src/game.vcxproj @@ -46,7 +46,6 @@ ../ $(ProjectName)_x64 - c:\fmtlib\;c:\jsoncpp\;$(ExternalIncludePath) ../ @@ -65,23 +64,18 @@ Level3 true - KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;NO_FMT_SOURCE;FMT_HEADER_ONLY;%(PreprocessorDefinitions) + KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true stdcpp17 4267;4244 true MultiThreadedDebug - c:\jsoncpp;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) NotSet true - c:\jsoncpp;%(AdditionalLibraryDirectories) - %(AdditionalDependencies) - - false - @@ -89,12 +83,13 @@ true true true - KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;FMT_HEADER_ONLY;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true stdcpp17 4267;4244 true MultiThreaded + /utf-8 %(AdditionalOptions) NotSet From 882dbc6f620f75569e337ada4859cb05c70e7111 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:50:39 +0000 Subject: [PATCH 022/142] Ensure override checks see updated map name --- src/g_spawn.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..6aaa04f 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1691,6 +1691,13 @@ parsing textual entity definitions out of an ent file. void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint) { bool ent_file_exists = false, ent_valid = true; //const char *entities = level.entstring.c_str(); + + Q_strlcpy(level.mapname, mapname, sizeof(level.mapname)); + // Paril: fixes a bug where autosaves will start you at + // the wrong spawnpoint if they happen to be non-empty + // (mine2 -> mine3) + if (!game.autosaved) + Q_strlcpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); //#if 0 // load up ent override //const char *name = G_Fmt("baseq2/maps/{}.ent", mapname).data(); @@ -1774,13 +1781,6 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp // all other flags are not important atm globals.server_flags &= SERVER_FLAG_LOADING; - Q_strlcpy(level.mapname, mapname, sizeof(level.mapname)); - // Paril: fixes a bug where autosaves will start you at - // the wrong spawnpoint if they happen to be non-empty - // (mine2 -> mine3) - if (!game.autosaved) - Q_strlcpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); - level.is_n64 = strncmp(level.mapname, "q64/", 4) == 0; level.coop_scale_players = 0; From 4a9f027175ba95fe333a6bc2a8b061a7bd87ca7d Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:51:06 +0000 Subject: [PATCH 023/142] Reset num_entities after clearing entity array --- src/g_spawn.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..c8d5395 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1769,7 +1769,8 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp gi.FreeTags(TAG_LEVEL); memset(&level, 0, sizeof(level)); - memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); +memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); +globals.num_entities = game.maxclients + 1; // all other flags are not important atm globals.server_flags &= SERVER_FLAG_LOADING; From c459cdea8bff9460a4236a71385befa7a788bbe9 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:51:28 +0000 Subject: [PATCH 024/142] Fix server loading flag handling --- src/g_spawn.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..5ac0c39 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1770,9 +1770,9 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp memset(&level, 0, sizeof(level)); memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); - + // all other flags are not important atm - globals.server_flags &= SERVER_FLAG_LOADING; + globals.server_flags |= SERVER_FLAG_LOADING; Q_strlcpy(level.mapname, mapname, sizeof(level.mapname)); // Paril: fixes a bug where autosaves will start you at @@ -2432,4 +2432,6 @@ void SP_worldspawn(gentity_t *ent) { gi.configstring(CONFIG_COOP_RESPAWN_STRING + 3, "$g_coop_respawn_waiting"); gi.configstring(CONFIG_COOP_RESPAWN_STRING + 4, "$g_coop_respawn_no_lives"); } + + globals.server_flags &= ~SERVER_FLAG_LOADING; } From 104bd5c3a1863ded319c436d0fa7cb4ae5570c2d Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:52:08 +0000 Subject: [PATCH 025/142] Reset level state without memset --- src/g_spawn.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..2261521 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1680,6 +1680,20 @@ void ClearWorldEntities() { } } +/* +============= +ResetLevelState + +Value-initializes the global level state and reapplies defaults for +non-trivial members. +============= +*/ +static void ResetLevelState() { + level = level_locals_t{}; + level.monsters_registered.fill(nullptr); + level.health_bar_entities.fill(nullptr); +} + /* ============== SpawnEntities @@ -1768,7 +1782,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp gi.FreeTags(TAG_LEVEL); - memset(&level, 0, sizeof(level)); + ResetLevelState(); memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); // all other flags are not important atm From 895a5fcb80b39432a1db9d6049ee00b00dad7095 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:53:22 +0000 Subject: [PATCH 026/142] Preserve level entity string --- src/g_spawn.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..c42aef3 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1751,7 +1751,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp //gi.Com_PrintFmt("{}: Entities override file not saved as file already exists: \"{}\"\n", __FUNCTION__, name); } } - level.entstring = entities; + std::string incoming_entstring = entities ? std::string(entities) : std::string(); //#endif //ParseWorldEntityString(mapname, RS(RS_Q3A)); @@ -1770,6 +1770,8 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp memset(&level, 0, sizeof(level)); memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); + level.entstring = incoming_entstring; + entities = level.entstring.c_str(); // all other flags are not important atm globals.server_flags &= SERVER_FLAG_LOADING; From d939e1c96644d8376d5fcfab78784081e221c270 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:56:28 +0000 Subject: [PATCH 027/142] Refactor SpawnEntities entity parsing --- src/g_spawn.cpp | 140 ++++++++---------------------------------------- 1 file changed, 23 insertions(+), 117 deletions(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..18e34cb 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1547,10 +1547,20 @@ static void G_LocateSpawnSpots(void) { level.num_spawn_spots = n; } +/* +============= +ParseWorldEntityString + +Loads the base entity string for the level and optionally overrides it with +an external .ent file. +============= +*/ static void ParseWorldEntityString(const char *mapname, bool try_q3) { bool ent_file_exists = false, ent_valid = true; const char *entities = level.entstring.c_str(); + (void)try_q3; + // load up ent override const char *name = G_Fmt("baseq2/{}/{}.ent", g_entity_override_dir->string[0] ? g_entity_override_dir->string : "maps", mapname).data(); FILE *f = fopen(name, "rb"); @@ -1612,6 +1622,13 @@ static void ParseWorldEntityString(const char *mapname, bool try_q3) { level.entstring = entities; } +/* +============= +ParseWorldEntities + +Creates runtime entities from the currently loaded entity string. +============= +*/ static void ParseWorldEntities() { gentity_t *ent = nullptr; int inhibit = 0; @@ -1689,71 +1706,7 @@ parsing textual entity definitions out of an ent file. ============== */ void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint) { - bool ent_file_exists = false, ent_valid = true; - //const char *entities = level.entstring.c_str(); -//#if 0 - // load up ent override - //const char *name = G_Fmt("baseq2/maps/{}.ent", mapname).data(); - const char *name = G_Fmt("baseq2/{}/{}.ent", g_entity_override_dir->string[0] ? g_entity_override_dir->string : "maps", mapname).data(); - FILE *f = fopen(name, "rb"); - if (f != NULL) { - char *buffer = nullptr; - size_t length; - size_t read_length; - - fseek(f, 0, SEEK_END); - length = ftell(f); - fseek(f, 0, SEEK_SET); - - if (length > 0x40000) { - //gi.Com_PrintFmt("{}: Entities override file length exceeds maximum: \"{}\"\n", __FUNCTION__, name); - ent_valid = false; - } - if (ent_valid) { - buffer = (char *)gi.TagMalloc(length + 1, '\0'); - if (length) { - read_length = fread(buffer, 1, length, f); - - if (length != read_length) { - //gi.Com_PrintFmt("{}: Entities override file read error: \"{}\"\n", __FUNCTION__, name); - ent_valid = false; - } - } - } - ent_file_exists = true; - fclose(f); - - if (ent_valid) { - if (g_entity_override_load->integer && !strstr(level.mapname, ".dm2")) { - - if (VerifyEntityString((const char *)buffer)) { - entities = (const char *)buffer; - if (g_verbose->integer) - gi.Com_PrintFmt("{}: Entities override file verified and loaded: \"{}\"\n", __FUNCTION__, name); - } - } - } else { - gi.Com_PrintFmt("{}: Entities override file load error for \"{}\", discarding.\n", __FUNCTION__, name); - } - } - - // save ent override - if (g_entity_override_save->integer && !strstr(level.mapname, ".dm2")) { - if (!ent_file_exists) { - f = fopen(name, "w"); - if (f) { - fwrite(entities, 1, strlen(entities), f); - if (g_verbose->integer) - gi.Com_PrintFmt("{}: Entities override file written to: \"{}\"\n", __FUNCTION__, name); - fclose(f); - } - } else { - //gi.Com_PrintFmt("{}: Entities override file not saved as file already exists: \"{}\"\n", __FUNCTION__, name); - } - } - level.entstring = entities; -//#endif - //ParseWorldEntityString(mapname, RS(RS_Q3A)); + std::string new_entstring = entities ? entities : ""; // clear cached indices cached_soundindex::clear_all(); @@ -1770,7 +1723,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp memset(&level, 0, sizeof(level)); memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); - + // all other flags are not important atm globals.server_flags &= SERVER_FLAG_LOADING; @@ -1781,6 +1734,9 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp if (!game.autosaved) Q_strlcpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); + level.entstring = new_entstring; + ParseWorldEntityString(mapname, RS(RS_Q3A)); + level.is_n64 = strncmp(level.mapname, "q64/", 4) == 0; level.coop_scale_players = 0; @@ -1798,57 +1754,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp // reserve some spots for dead player bodies for coop / deathmatch InitBodyQue(); - gentity_t *ent = nullptr; - int inhibit = 0; - const char *com_token; - //const char *entities = level.entstring.c_str(); - - // parse entities - while (1) { - // parse the opening brace - com_token = COM_Parse(&entities); - if (!entities) - break; - if (com_token[0] != '{') - gi.Com_ErrorFmt("{}: Found \"{}\" when expecting {{ in entity string.\n", __FUNCTION__, com_token); - - if (!ent) - ent = g_entities; - else - ent = G_Spawn(); - entities = ED_ParseEntity(entities, ent); - - // nasty hacks time! - if (!strcmp(level.mapname, "bunk1")) { - if (!strcmp(ent->classname, "func_button") && !Q_strcasecmp(ent->model, "*36")) { - ent->wait = -1; - } - } - - // remove things (except the world) from different skill levels or deathmatch - if (ent != g_entities) { - if (G_InhibitEntity(ent)) { - G_FreeEntity(ent); - inhibit++; - continue; - } - - ent->spawnflags &= ~SPAWNFLAG_EDITOR_MASK; - } - - if (!ent) - gi.Com_ErrorFmt("{}: Invalid or empty entity string.", __FUNCTION__); - - // do this before calling the spawn function so it can be overridden. - ent->gravityVector = { 0.0, 0.0, -1.0 }; - - ED_CallSpawn(ent); - - ent->s.renderfx |= RF_IR_VISIBLE; - } - - if (inhibit && g_verbose->integer) - gi.Com_PrintFmt("{} entities inhibited.\n", inhibit); + ParseWorldEntities(); // precache start_items PrecacheStartItems(); From e41cb45a71554ad73bf427377e22c52d3089cac6 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:58:11 +0000 Subject: [PATCH 028/142] Fix intermission angles --- src/g_main.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index e5f9e40..92a3642 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -2246,8 +2246,7 @@ void FindIntermissionPoint(void) { if (target) { gi.Com_Print("FindIntermissionPoint target 2\n"); dir = (target->s.origin - level.intermission_origin).normalized(); - AngleVectors(dir); - level.intermission_angle = dir; + level.intermission_angle = vectoangles(dir); } } } @@ -2305,8 +2304,7 @@ void SetIntermissionPoint(void) { if (target) { //gi.Com_Print("HAS TARGET\n"); vec3_t dir = (target->s.origin - level.intermission_origin).normalized(); - AngleVectors(dir); - level.intermission_angle = dir; + level.intermission_angle = vectoangles(dir); } } if (ent && !level.intermission_angle) From 5f3694da916f2dcb6af502bb5e2eee6061d440b6 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:58:32 +0000 Subject: [PATCH 029/142] Guard intermission pitch hacks when spawn missing --- src/g_main.cpp | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index e5f9e40..e79b01e 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -2286,31 +2286,31 @@ void SetIntermissionPoint(void) { if (ent) { level.intermission_origin = ent->s.origin; level.spawn_spots[SPAWN_SPOT_INTERMISSION] = ent; - } - - // ugly hax! - if (!Q_strncasecmp(level.mapname, "campgrounds", 11)) { - gvec3_t v = { -320, -96, 503 }; - if (ent->s.origin == v) - level.intermission_angle[PITCH] = -30; - } else if (!Q_strncasecmp(level.mapname, "rdm10", 5)) { - gvec3_t v = { -1256, -1672, -136 }; - if (ent->s.origin == v) - level.intermission_angle = { 15, 135, 0 }; - } else { - // if it has a target, look towards it - if (ent && ent->target) { - gentity_t *target = G_PickTarget(ent->target); - if (target) { - //gi.Com_Print("HAS TARGET\n"); - vec3_t dir = (target->s.origin - level.intermission_origin).normalized(); - AngleVectors(dir); - level.intermission_angle = dir; + // ugly hax! + if (!Q_strncasecmp(level.mapname, "campgrounds", 11)) { + gvec3_t v = { -320, -96, 503 }; + if (ent->s.origin == v) + level.intermission_angle[PITCH] = -30; + } else if (!Q_strncasecmp(level.mapname, "rdm10", 5)) { + gvec3_t v = { -1256, -1672, -136 }; + if (ent->s.origin == v) + level.intermission_angle = { 15, 135, 0 }; + } else { + // if it has a target, look towards it + if (ent->target) { + gentity_t *target = G_PickTarget(ent->target); + + if (target) { + //gi.Com_Print("HAS TARGET\n"); + vec3_t dir = (target->s.origin - level.intermission_origin).normalized(); + AngleVectors(dir); + level.intermission_angle = dir; + } } + if (!level.intermission_angle) + level.intermission_angle = ent->s.angles; } - if (ent && !level.intermission_angle) - level.intermission_angle = ent->s.angles; } //gi.Com_PrintFmt("{}: origin={} angles={}\n", __FUNCTION__, level.intermission_origin, level.intermission_angle); From ee56ee24bbf9ebf94641f613dd3bc1e821833a87 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 11:59:45 +0000 Subject: [PATCH 030/142] Clamp maxclients to safe bounds --- src/g_main.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index e5f9e40..27560d2 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -4,6 +4,7 @@ #include "g_local.h" #include "bots/bot_includes.h" #include "monsters/m_player.h" // match starts +#include CHECK_GCLIENT_INTEGRITY; CHECK_ENTITY_INTEGRITY; @@ -1053,6 +1054,8 @@ static void InitGame() { game = {}; + const int32_t clamped_maxclients = std::clamp(maxclients->integer, 1, MAX_CLIENTS); + // initialize all entities for this game game.maxentities = maxentities->integer; g_entities = (gentity_t *)gi.TagMalloc(game.maxentities * sizeof(g_entities[0]), TAG_GAME); @@ -1060,13 +1063,13 @@ static void InitGame() { globals.max_entities = game.maxentities; // initialize all clients for this game - game.maxclients = maxclients->integer; - game.clients = (gclient_t *)gi.TagMalloc(game.maxclients * sizeof(game.clients[0]), TAG_GAME); - globals.num_entities = game.maxclients + 1; + game.maxclients = clamped_maxclients; + game.clients = (gclient_t *)gi.TagMalloc(clamped_maxclients * sizeof(game.clients[0]), TAG_GAME); + globals.num_entities = clamped_maxclients + 1; // how far back we should support lag origins for game.max_lag_origins = 20 * (0.1f / gi.frame_time_s); - game.lag_origins = (vec3_t *)gi.TagMalloc(game.maxclients * sizeof(vec3_t) * game.max_lag_origins, TAG_GAME); + game.lag_origins = (vec3_t *)gi.TagMalloc(clamped_maxclients * sizeof(vec3_t) * game.max_lag_origins, TAG_GAME); level.start_time = level.time; From f81be4ed4126e1578091a22d1fcb1678f5dec74b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 12:00:15 +0000 Subject: [PATCH 031/142] Fix TagMalloc tags in load paths --- src/g_main.cpp | 15 +++++++++++++-- src/g_spawn.cpp | 13 +++++++++++-- src/p_client.cpp | 3 ++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index e5f9e40..6d5ff47 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -544,6 +544,13 @@ static bool Horde_AllMonstersDead() { // ================================================= +/* +============= +G_LoadMOTD + +Loads the server message of the day text into persistent memory. +============= +*/ void G_LoadMOTD() { // load up ent override const char *name = G_Fmt("baseq2/{}", g_motd_filename->string[0] ? g_motd_filename->string : "motd.txt").data(); @@ -563,7 +570,7 @@ void G_LoadMOTD() { valid = false; } if (valid) { - buffer = (char *)gi.TagMalloc(length + 1, '\0'); + buffer = (char *)gi.TagMalloc(length + 1, TAG_GAME); if (length) { read_length = fread(buffer, 1, length, f); @@ -572,9 +579,10 @@ void G_LoadMOTD() { valid = false; } } + buffer[length] = '\0'; } fclose(f); - + if (valid) { game.motd = (const char *)buffer; game.motd_mod_count++; @@ -582,10 +590,13 @@ void G_LoadMOTD() { gi.Com_PrintFmt("{}: MotD file verified and loaded: \"{}\"\n", __FUNCTION__, name); } else { gi.Com_PrintFmt("{}: MotD file load error for \"{}\", discarding.\n", __FUNCTION__, name); + if (buffer) + gi.TagFree(buffer); } } } + int check_ruleset = -1; static void CheckRuleset() { if (game.ruleset && check_ruleset == g_ruleset->modified_count) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 163e35a..5bbbf0d 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1547,6 +1547,13 @@ static void G_LocateSpawnSpots(void) { level.num_spawn_spots = n; } +/* +============= +ParseWorldEntityString + +Loads an entity override file and applies it to the current map if valid. +============= +*/ static void ParseWorldEntityString(const char *mapname, bool try_q3) { bool ent_file_exists = false, ent_valid = true; const char *entities = level.entstring.c_str(); @@ -1568,7 +1575,7 @@ static void ParseWorldEntityString(const char *mapname, bool try_q3) { ent_valid = false; } if (ent_valid) { - buffer = (char *)gi.TagMalloc(length + 1, '\0'); + buffer = (char *)gi.TagMalloc(length + 1, TAG_LEVEL); if (length) { read_length = fread(buffer, 1, length, f); @@ -1577,6 +1584,7 @@ static void ParseWorldEntityString(const char *mapname, bool try_q3) { ent_valid = false; } } + buffer[length] = '\0'; } ent_file_exists = true; fclose(f); @@ -1710,7 +1718,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp ent_valid = false; } if (ent_valid) { - buffer = (char *)gi.TagMalloc(length + 1, '\0'); + buffer = (char *)gi.TagMalloc(length + 1, TAG_LEVEL); if (length) { read_length = fread(buffer, 1, length, f); @@ -1719,6 +1727,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp ent_valid = false; } } + buffer[length] = '\0'; } ent_file_exists = true; fclose(f); diff --git a/src/p_client.cpp b/src/p_client.cpp index 74f5e2f..f1ebd45 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -273,7 +273,7 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { cfg_valid = false; } if (cfg_valid) { - buffer = (char*)gi.TagMalloc(length + 1, '\0'); + buffer = (char*)gi.TagMalloc(length + 1, TAG_GAME); if (length) { read_length = fread(buffer, 1, length, f); @@ -281,6 +281,7 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { cfg_valid = false; } } + buffer[length] = '\0'; } file_exists = true; fclose(f); From 3691b9a63fdc0125822290cca4b929b95d807a7b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:17:29 +0000 Subject: [PATCH 032/142] Fix team balancing loop guard --- src/g_cmds.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 54ffdb6..52d19c3 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -1927,11 +1927,16 @@ int TeamBalance(bool force) { qsort(index, count, sizeof(index[0]), PlayerSortByJoinTime); //run through sort list, switching from stack_team until teams are even + if (!count) { + gi.LocBroadcast_Print(PRINT_HIGH, "Team balance skipped: no stacked players available.\n"); + return 0; + } + if (count) { size_t i; int switched = 0; gclient_t *cl = nullptr; - for (i = 0; i < count, delta > 1; i++) { + for (i = 0; i < count && delta > 1; i++) { cl = &game.clients[index[i]]; if (!cl) From 68ef6799d66160e5e9198b651f53d3b30eda7319 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:17:51 +0000 Subject: [PATCH 033/142] Ensure map restart resets world state --- src/g_cmds.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 54ffdb6..2a18604 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -3222,13 +3222,20 @@ static void Cmd_SetMap_f(gentity_t *ent) { } extern void ClearWorldEntities(); + +/* +============= +Cmd_MapRestart_f + +Reset the match and world state before reloading the current map. +============= +*/ static void Cmd_MapRestart_f(gentity_t *ent) { gi.Broadcast_Print(PRINT_HIGH, "[ADMIN]: Session reset.\n"); - //TODO: reset match variables, clear world entities, reload world entities - //SpawnEntities(level.mapname, level.entstring.c_str(), nullptr); - //Match_Reset(); - //ClearWorldEntities(); + Match_Reset(); + ClearWorldEntities(); + SpawnEntities(level.mapname, level.entstring.c_str(), nullptr); gi.AddCommandString(G_Fmt("gamemap {}\n", level.mapname).data()); } From 6ffc27aa34708d870ba1bca3ed8e4f2d08efb2ed Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:18:18 +0000 Subject: [PATCH 034/142] Improve map list validation in setmap command --- src/g_cmds.cpp | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 54ffdb6..ffd2a1d 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -3206,13 +3206,50 @@ static void Cmd_Ruleset_f(gentity_t *ent) { gi.cvar_forceset("g_ruleset", G_Fmt("{}", (int)rs).data()); } +/* +============= +G_MapListContains + +Checks if the provided map name is present in g_map_list as a discrete entry. +============= +*/ +static bool G_MapListContains(const char *mapname) { + if (!mapname || !mapname[0]) + return false; + + if (!g_map_list->string[0]) + return true; + + const char *map_list = g_map_list->string; + char *token; + + while (true) { + token = COM_ParseEx(&map_list, " "); + + if (!*token) + break; + + if (!strcmp(token, mapname) || !Q_strcasecmp(token, mapname)) + return true; + } + + return false; +} + +/* +============= +Cmd_SetMap_f + +Changes to a map within the map list. +============= +*/ static void Cmd_SetMap_f(gentity_t *ent) { if (gi.argc() < 2) { gi.LocClient_Print(ent, PRINT_HIGH, "Usage: {} [mapname]\nChanges to a map within the map list.", gi.argv(0)); return; } - if (g_map_list->string[0] && !strstr(g_map_list->string, gi.argv(1))) { + if (!G_MapListContains(gi.argv(1))) { gi.Client_Print(ent, PRINT_HIGH, "Map name is not valid.\n"); return; } From 8d69ec5fca0b5cb1958fccbcdbc6df4e4044b8d0 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:20:00 +0000 Subject: [PATCH 035/142] Defer team balance during rounds --- src/g_cmds.cpp | 161 ++++++++++++++++++++++++++++++++++++++++++++++--- src/g_local.h | 1 + src/g_main.cpp | 2 + 3 files changed, 154 insertions(+), 10 deletions(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 54ffdb6..f00ea44 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -1889,6 +1889,14 @@ bool AllowClientTeamSwitch(gentity_t *ent) { return true; } +struct team_balance_request_t { + int client_index; + team_t team; +}; + +static team_balance_request_t balance_queue[MAX_CLIENTS_KEX]; +static size_t balance_queue_count = 0; + /* ================ TeamBalance @@ -1897,6 +1905,50 @@ Balance the teams without shuffling. Switch last joined player(s) from stacked team. ================ */ +/* +============= +FindBalanceRequest + +Finds an existing queued balance request for the supplied client index +============= +*/ +static team_balance_request_t *FindBalanceRequest(int client_index) { + for (size_t i = 0; i < balance_queue_count; i++) { + if (balance_queue[i].client_index == client_index) + return &balance_queue[i]; + } + + return nullptr; +} + +/* +============= +EnqueueBalanceRequest + +Queues a balance change for the supplied client index +============= +*/ +static void EnqueueBalanceRequest(int client_index, team_t team) { + team_balance_request_t *existing_request = FindBalanceRequest(client_index); + + if (existing_request) { + existing_request->team = team; + return; + } + + if (balance_queue_count >= MAX_CLIENTS_KEX) + return; + + balance_queue[balance_queue_count].client_index = client_index; + balance_queue[balance_queue_count].team = team; + balance_queue_count++; +} + +/* +============= +TeamBalance +============= +*/ int TeamBalance(bool force) { if (!Teams()) return 0; @@ -1904,15 +1956,48 @@ int TeamBalance(bool force) { if (GT(GT_RR)) return 0; - int delta = abs(level.num_playing_red - level.num_playing_blue); + bool queue_changes = GTF(GTF_ROUNDS) && level.round_state == roundst_t::ROUND_IN_PROGRESS; + + int red_count = level.num_playing_red; + int blue_count = level.num_playing_blue; + + if (queue_changes) { + for (size_t i = 0; i < balance_queue_count; i++) { + gclient_t *queued_client = &game.clients[balance_queue[i].client_index]; + + switch (queued_client->sess.team) { + case TEAM_RED: + red_count--; + break; + case TEAM_BLUE: + blue_count--; + break; + default: + break; + } + + switch (balance_queue[i].team) { + case TEAM_RED: + red_count++; + break; + case TEAM_BLUE: + blue_count++; + break; + default: + break; + } + } + } + + int delta = abs(red_count - blue_count); if (delta < 2) return level.num_playing_red - level.num_playing_blue; - team_t stack_team = level.num_playing_red > level.num_playing_blue ? TEAM_RED : TEAM_BLUE; + team_t stack_team = red_count > blue_count ? TEAM_RED : TEAM_BLUE; - size_t count = 0; - int index[MAX_CLIENTS_KEX/2]; + size_t count = 0; + int index[MAX_CLIENTS_KEX/2]; memset(index, 0, sizeof(index)); // assemble list of client nums of everyone on stacked team @@ -1928,7 +2013,7 @@ int TeamBalance(bool force) { //run through sort list, switching from stack_team until teams are even if (count) { - size_t i; + size_t i; int switched = 0; gclient_t *cl = nullptr; for (i = 0; i < count, delta > 1; i++) { @@ -1943,24 +2028,80 @@ int TeamBalance(bool force) { if (cl->sess.team != stack_team) continue; - cl->sess.team = stack_team == TEAM_RED ? TEAM_BLUE : TEAM_RED; + if (queue_changes) { + team_balance_request_t *queued_request = FindBalanceRequest(cl - game.clients); - //TODO: queue this change in round-based games - ClientRespawn(&g_entities[cl - game.clients + 1]); - gi.LocClient_Print(&g_entities[cl - game.clients + 1], PRINT_CENTER, "You have changed teams to rebalance the game.\n"); + if (queued_request && queued_request->team != stack_team) + continue; + } + + team_t target_team = stack_team == TEAM_RED ? TEAM_BLUE : TEAM_RED; + + if (queue_changes) { + EnqueueBalanceRequest(cl - game.clients, target_team); + gi.LocClient_Print(&g_entities[cl - game.clients + 1], PRINT_CENTER, "You will change teams after this round ends to rebalance the game.\n"); + } else { + cl->sess.team = target_team; + + ClientRespawn(&g_entities[cl - game.clients + 1]); + gi.LocClient_Print(&g_entities[cl - game.clients + 1], PRINT_CENTER, "You have changed teams to rebalance the game.\n"); + } delta--; switched++; } if (switched) { - gi.LocBroadcast_Print(PRINT_HIGH, "Teams have been balanced.\n"); + gi.LocBroadcast_Print(PRINT_HIGH, queue_changes ? "Team balance queued for end of round.\n" : "Teams have been balanced.\n"); return switched; } } return 0; } +/* +============= +ProcessBalanceQueue + +Applies queued team balance changes once the round has ended +============= +*/ +void ProcessBalanceQueue(void) { + if (!balance_queue_count) + return; + + if (GTF(GTF_ROUNDS) && level.round_state == roundst_t::ROUND_IN_PROGRESS) + return; + + size_t applied = 0; + + for (size_t i = 0; i < balance_queue_count; i++) { + if (balance_queue[i].client_index < 0 || balance_queue[i].client_index >= MAX_CLIENTS_KEX) + continue; + + gclient_t *cl = &game.clients[balance_queue[i].client_index]; + gentity_t *ent = &g_entities[balance_queue[i].client_index + 1]; + + if (!cl->pers.connected || !ent->inuse || !ent->client) + continue; + + if (cl->sess.team == balance_queue[i].team) + continue; + + cl->sess.team = balance_queue[i].team; + + ClientRespawn(ent); + gi.LocClient_Print(ent, PRINT_CENTER, "You have changed teams to rebalance the game.\n"); + + applied++; + } + + balance_queue_count = 0; + + if (applied) + gi.LocBroadcast_Print(PRINT_HIGH, "Teams have been balanced.\n"); +} + /* ================ TeamShuffle diff --git a/src/g_local.h b/src/g_local.h index 211be5f..dce2df4 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -2531,6 +2531,7 @@ team_t PickTeam(int ignoreClientNum); void BroadcastTeamChange(gentity_t *ent, int old_team, bool inactive, bool silent); bool AllowClientTeamSwitch(gentity_t *ent); int TeamBalance(bool force); +void ProcessBalanceQueue(void); void Cmd_ReadyUp_f(gentity_t *ent); void VoteCommandStore(gentity_t *ent); diff --git a/src/g_main.cpp b/src/g_main.cpp index a72073c..e819135 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -1469,6 +1469,8 @@ void Round_End() { level.round_state = roundst_t::ROUND_ENDED; level.round_state_timer = level.time + 3_sec; level.horde_all_spawned = false; + + ProcessBalanceQueue(); } /* From 23def0cca210895e43268a00699393d0443fb2e3 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:20:29 +0000 Subject: [PATCH 036/142] Handle duplicate save registry entries --- src/g_save.cpp | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index 8515559..442c5cf 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -60,6 +60,13 @@ static std::unordered_map, const save_ #include +/* +============= +InitSave + +Initializes the save registry and reports duplicate entries to keep hash tables consistent. +============= +*/ void InitSave() { if (save_data_initialized) return; @@ -67,32 +74,32 @@ void InitSave() { for (const save_data_list_t *link = list_head; link; link = link->next) { const void *link_ptr = link; - if (list_hash.find(link_ptr) != list_hash.end()) { - auto existing = *list_hash.find(link_ptr); - - // [0] is just to silence warning - assert(false || "invalid save pointer; break here to find which pointer it is"[0]); + auto existing_ptr = list_hash.find(link_ptr); + if (existing_ptr != list_hash.end()) { + const save_data_list_t *existing = existing_ptr->second; if (!deathmatch->integer) { if (g_strict_saves->integer) - gi.Com_ErrorFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + gi.Com_ErrorFmt("duplicate save link pointer {} for type {} (tag {}) already mapped to {} (tag {})", link_ptr, link->name, (int32_t)link->tag, existing->name, (int32_t)existing->tag); else - gi.Com_PrintFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + gi.Com_PrintFmt("duplicate save link pointer {} for type {} (tag {}) already mapped to {} (tag {})", link_ptr, link->name, (int32_t)link->tag, existing->name, (int32_t)existing->tag); } - } - if (list_str_hash.find(link->name) != list_str_hash.end()) { - auto existing = *list_str_hash.find(link->name); + continue; + } - // [0] is just to silence warning - assert(false || "invalid save pointer; break here to find which pointer it is"[0]); + auto existing_name = list_str_hash.find(link->name); + if (existing_name != list_str_hash.end()) { + const save_data_list_t *existing = existing_name->second; if (!deathmatch->integer) { if (g_strict_saves->integer) - gi.Com_ErrorFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + gi.Com_ErrorFmt("duplicate save type name {} (tag {}) already linked to pointer {}, cannot add pointer {}", link->name, (int32_t)link->tag, existing, link_ptr); else - gi.Com_PrintFmt("link pointer {} already linked as {}; fatal error", link_ptr, existing.second->name); + gi.Com_PrintFmt("duplicate save type name {} (tag {}) already linked to pointer {}, cannot add pointer {}", link->name, (int32_t)link->tag, existing, link_ptr); } + + continue; } list_hash.emplace(link_ptr, link); @@ -102,10 +109,9 @@ void InitSave() { save_data_initialized = true; } - // initializer for save data save_data_list_t::save_data_list_t(const char *name_in, save_data_tag_t tag_in, const void *ptr_in) : - name(name_in), + name(name_in), tag(tag_in), ptr(ptr_in) { if (save_data_initialized) From 796cfe886fe039fd43424d15639b3b3b73856a2d Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:21:15 +0000 Subject: [PATCH 037/142] Handle null trigger activators safely --- src/g_activation.cpp | 32 +++++++++++++++++++++ src/g_activation.h | 25 ++++++++++++++++ src/g_utils.cpp | 46 ++++++++++++++++++++---------- src/game.vcxproj | 2 ++ src/game.vcxproj.filters | 2 ++ tests/activation_message_tests.cpp | 30 +++++++++++++++++++ 6 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 src/g_activation.cpp create mode 100644 src/g_activation.h create mode 100644 tests/activation_message_tests.cpp diff --git a/src/g_activation.cpp b/src/g_activation.cpp new file mode 100644 index 0000000..8f9e06e --- /dev/null +++ b/src/g_activation.cpp @@ -0,0 +1,32 @@ +#include "g_activation.h" + +/* +============= +BuildActivationMessagePlan + +Creates an activation message plan for the provided context. +============= +*/ +activation_message_plan_t BuildActivationMessagePlan(bool has_message, bool has_activator, bool activator_is_monster, bool coop_global, bool coop_enabled, int noise_index) +{ + activation_message_plan_t plan; + + if (!has_message) + return plan; + + if (coop_global && coop_enabled) + plan.broadcast_global = true; + + if (!has_activator || activator_is_monster) + return plan; + + plan.center_on_activator = true; + + if (noise_index >= 0) + { + plan.play_sound = true; + plan.sound_index = noise_index; + } + + return plan; +} diff --git a/src/g_activation.h b/src/g_activation.h new file mode 100644 index 0000000..72665b4 --- /dev/null +++ b/src/g_activation.h @@ -0,0 +1,25 @@ +/* +============= +BuildActivationMessagePlan + +Defines the information needed to safely emit activation messaging and sounds. +============= +*/ +#pragma once + +struct activation_message_plan_t +{ + bool broadcast_global = false; + bool center_on_activator = false; + bool play_sound = false; + int sound_index = -1; +}; + +/* +============= +BuildActivationMessagePlan + +Creates an activation message plan for the provided context. +============= +*/ +activation_message_plan_t BuildActivationMessagePlan(bool has_message, bool has_activator, bool activator_is_monster, bool coop_global, bool coop_enabled, int noise_index); diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 88c3736..32ff36c 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -2,6 +2,7 @@ // Licensed under the GNU General Public License 2.0. // g_utils.c -- misc utility functions for game module +#include "g_activation.h" #include "g_local.h" #include @@ -110,23 +111,38 @@ static THINK(Think_Delay) (gentity_t *ent) -> void { G_FreeEntity(ent); } +/* +============= +G_PrintActivationMessage + +Prints activation messaging and plays optional sounds when an entity is used. +============= +*/ void G_PrintActivationMessage(gentity_t *ent, gentity_t *activator, bool coop_global) { - // - // print the message - // - if ((ent->message) && !(activator->svflags & SVF_MONSTER)) { - if (coop_global && coop->integer) - gi.LocBroadcast_Print(PRINT_CENTER, "{}", ent->message); + if (!ent || !ent->message) + return; + + const bool has_activator = activator != nullptr; + const bool activator_is_monster = has_activator && (activator->svflags & SVF_MONSTER); + const activation_message_plan_t plan = BuildActivationMessagePlan(true, has_activator, activator_is_monster, coop_global, coop->integer, ent->noise_index); + + if (!plan.broadcast_global && !plan.center_on_activator) + return; + + if (plan.broadcast_global) + gi.LocBroadcast_Print(PRINT_CENTER, "{}", ent->message); + + if (!has_activator || !plan.center_on_activator) + return; + + gi.LocCenter_Print(activator, "{}", ent->message); + + // [Paril-KEX] allow non-noisy centerprints + if (plan.play_sound) { + if (plan.sound_index) + gi.sound(activator, CHAN_AUTO, plan.sound_index, 1, ATTN_NORM, 0); else - gi.LocCenter_Print(activator, "{}", ent->message); - - // [Paril-KEX] allow non-noisy centerprints - if (ent->noise_index >= 0) { - if (ent->noise_index) - gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); - else - gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); - } + gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); } } diff --git a/src/game.vcxproj b/src/game.vcxproj index e7434c6..e08d35f 100644 --- a/src/game.vcxproj +++ b/src/game.vcxproj @@ -107,6 +107,7 @@ + @@ -173,6 +174,7 @@ + diff --git a/src/game.vcxproj.filters b/src/game.vcxproj.filters index 68937b2..c2dc86b 100644 --- a/src/game.vcxproj.filters +++ b/src/game.vcxproj.filters @@ -6,6 +6,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/tests/activation_message_tests.cpp b/tests/activation_message_tests.cpp new file mode 100644 index 0000000..be25e2b --- /dev/null +++ b/tests/activation_message_tests.cpp @@ -0,0 +1,30 @@ +#include "../src/g_activation.h" +#include + +/* +============= +main + +Regression coverage for activation message planning. +============= +*/ +int main() +{ + activation_message_plan_t no_activator_broadcast = BuildActivationMessagePlan(true, false, false, true, true, 5); + assert(no_activator_broadcast.broadcast_global); + assert(!no_activator_broadcast.center_on_activator); + assert(!no_activator_broadcast.play_sound); + + activation_message_plan_t no_activator_silent = BuildActivationMessagePlan(true, false, false, false, true, 2); + assert(!no_activator_silent.broadcast_global); + assert(!no_activator_silent.center_on_activator); + assert(!no_activator_silent.play_sound); + + activation_message_plan_t player_plan = BuildActivationMessagePlan(true, true, false, false, true, 0); + assert(!player_plan.broadcast_global); + assert(player_plan.center_on_activator); + assert(player_plan.play_sound); + assert(player_plan.sound_index == 0); + + return 0; +} From e3110ce53ec42028b3ada50277960728382786d3 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:22:06 +0000 Subject: [PATCH 038/142] Use dynamic choice list for target selection --- src/g_utils.cpp | 16 ++++----- src/g_utils_target_selection.h | 22 ++++++++++++ src/tests/g_utils_target_selection_test.cpp | 39 +++++++++++++++++++++ 3 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 src/g_utils_target_selection.h create mode 100644 src/tests/g_utils_target_selection_test.cpp diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 88c3736..afbd13c 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -4,6 +4,8 @@ #include "g_local.h" #include +#include +#include "g_utils_target_selection.h" /* ============= @@ -76,12 +78,9 @@ nullptr will be returned if the end of the list is reached. ============= */ -constexpr size_t MAXCHOICES = 8; - gentity_t *G_PickTarget(const char *targetname) { - gentity_t *choice[MAXCHOICES]; + std::vector choices; gentity_t *ent = nullptr; - int num_choices = 0; if (!targetname) { gi.Com_PrintFmt("{}: called with nullptr targetname.\n", __FUNCTION__); @@ -92,17 +91,16 @@ gentity_t *G_PickTarget(const char *targetname) { ent = G_FindByString<&gentity_t::targetname>(ent, targetname); if (!ent) break; - choice[num_choices++] = ent; - if (num_choices == MAXCHOICES) - break; + choices.emplace_back(ent); } - if (!num_choices) { + if (choices.empty()) { gi.Com_PrintFmt("{}: target {} not found\n", __FUNCTION__, targetname); + assert(!choices.empty()); return nullptr; } - return choice[irandom(num_choices)]; + return G_SelectRandomTarget(choices, irandom); } static THINK(Think_Delay) (gentity_t *ent) -> void { diff --git a/src/g_utils_target_selection.h b/src/g_utils_target_selection.h new file mode 100644 index 0000000..e36b3c8 --- /dev/null +++ b/src/g_utils_target_selection.h @@ -0,0 +1,22 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +#include +#include +#include + +/* +============= +G_SelectRandomTarget + +Selects a random target from a list using the provided random generator. +============= +*/ +template +T *G_SelectRandomTarget(const std::vector &choices, RandomFunc &&random_func) { + assert(!choices.empty()); + return choices[std::forward(random_func)(choices.size())]; +} + diff --git a/src/tests/g_utils_target_selection_test.cpp b/src/tests/g_utils_target_selection_test.cpp new file mode 100644 index 0000000..09733b1 --- /dev/null +++ b/src/tests/g_utils_target_selection_test.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +#include "../g_utils_target_selection.h" + +struct DummyTarget { +}; + +/* +============= +main + +Validates that the target selection helper can pick entries beyond the first eight options. +============= +*/ +int main() { + DummyTarget targets[10]; + std::vector references; + references.reserve(std::size(targets)); + + for (auto &target : targets) + references.push_back(&target); + + auto cycling_random = [index = static_cast(0)](size_t max) mutable { + return (index++) % static_cast(max); + }; + + bool saw_ninth_entry = false; + + for (size_t i = 0; i < references.size(); i++) { + DummyTarget *choice = G_SelectRandomTarget(references, cycling_random); + if (choice == references[9]) + saw_ninth_entry = true; + } + + assert(saw_ninth_entry); + return 0; +} From 064342cc5b5a18dbe546ad59392143cdd1a290ab Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:22:35 +0000 Subject: [PATCH 039/142] Fix JSON stack pop handling --- src/g_save.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index 8515559..4af342b 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -60,7 +60,18 @@ static std::unordered_map, const save_ #include +/* +============= +InitSave + +Initializes save data lookups and builds hash maps used for save operations. +============= +*/ void InitSave() { +#ifndef NDEBUG + json_run_stack_tests(); +#endif + if (save_data_initialized) return; @@ -134,17 +145,38 @@ const save_data_list_t *save_data_list_t::fetch(const void *ptr, save_data_tag_t std::string json_error_stack; +/* +============= +json_push_stack + +Pushes a new context onto the JSON error stack. +============= +*/ void json_push_stack(const std::string &stack) { json_error_stack += "::" + stack; } +/* +============= +json_pop_stack + +Removes the most recent context, including its preceding delimiter, from the JSON error stack. +============= +*/ void json_pop_stack() { - size_t o = json_error_stack.find_last_of("::"); + const size_t delimiter = json_error_stack.rfind("::"); - if (o != std::string::npos) - json_error_stack.resize(o - 1); + if (delimiter != std::string::npos) + json_error_stack.erase(delimiter); } +/* +============= +json_print_error + +Prints JSON load errors with the current stack context, optionally treating them as fatal. +============= +*/ void json_print_error(const char *field, const char *message, bool fatal) { if (fatal || g_strict_saves->integer) gi.Com_ErrorFmt("Error loading JSON\n{}.{}: {}", json_error_stack, field, message); @@ -152,6 +184,42 @@ void json_print_error(const char *field, const char *message, bool fatal) { gi.Com_PrintFmt("Warning loading JSON\n{}.{}: {}\n", json_error_stack, field, message); } +#ifndef NDEBUG +/* +============= +json_run_stack_tests + +Verifies nested JSON stack push/pop calls restore the stack without leaving delimiters behind. +============= +*/ +static void json_run_stack_tests() { + const std::string original_stack = json_error_stack; + + json_error_stack.clear(); + json_push_stack("outer"); + json_push_stack("inner"); + assert(json_error_stack == "::outer::inner"); + + json_pop_stack(); + assert(json_error_stack == "::outer"); + + json_pop_stack(); + assert(json_error_stack.empty()); + + json_error_stack = "::root"; + json_push_stack("child"); + json_push_stack("grandchild"); + json_pop_stack(); + assert(json_error_stack == "::root::child"); + json_pop_stack(); + assert(json_error_stack == "::root"); + json_pop_stack(); + assert(json_error_stack.empty()); + + json_error_stack = original_stack; +} +#endif + using save_void_t = save_data_t; enum save_type_id_t { From c5b2ab0df82593e6d65920971c9ad4e380f92152 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:23:30 +0000 Subject: [PATCH 040/142] Handle invalid save data lookups --- src/g_local.h | 16 ++++++-- src/g_save.cpp | 38 +++++++++++++++---- src/g_save_test.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 11 deletions(-) create mode 100644 src/g_save_test.cpp diff --git a/src/g_local.h b/src/g_local.h index 211be5f..4998012 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -829,8 +829,9 @@ struct save_data_list_t { save_data_tag_t tag; const void *ptr; // pointer to raw data const save_data_list_t *next; // next in list + bool valid; - save_data_list_t(const char *name, save_data_tag_t tag, const void *ptr); + save_data_list_t(const char *name, save_data_tag_t tag, const void *ptr, bool link = true, bool valid = true); static const save_data_list_t *fetch(const void *link_ptr, save_data_tag_t tag); }; @@ -859,12 +860,16 @@ struct save_data_t { save_data_t() {} constexpr save_data_t(const save_data_list_t *list_in) : - value(list_in->ptr), + value(list_in && list_in->valid ? list_in->ptr : nullptr), list(list_in) {} inline save_data_t(value_type ptr_in) : value(ptr_in), - list(ptr_in ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr) {} + list(ptr_in ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr) { + if (list && !list->valid) + value = nullptr; + } + inline save_data_t(const save_data_t &ref_in) : save_data_t(ref_in.value) {} @@ -873,6 +878,9 @@ struct save_data_t { if (value != ptr_in) { value = ptr_in; list = value ? save_data_list_t::fetch(reinterpret_cast(ptr_in), static_cast(Tag)) : nullptr; + + if (list && !list->valid) + value = nullptr; } return *this; @@ -880,7 +888,7 @@ struct save_data_t { constexpr const value_type pointer() const { return value; } constexpr const save_data_list_t *save_list() const { return list; } - constexpr const char *name() const { return value ? list->name : "null"; } + constexpr const char *name() const { return list ? list->name : "null"; } constexpr const value_type operator->() const { return value; } constexpr explicit operator bool() const { return value; } constexpr bool operator==(value_type ptr_in) const { return value == ptr_in; } diff --git a/src/g_save.cpp b/src/g_save.cpp index 8515559..f49dd24 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -60,6 +60,13 @@ static std::unordered_map, const save_ #include +/* +============= +InitSave + +Initializes the save data lists and validates for duplicates. +============= +*/ void InitSave() { if (save_data_initialized) return; @@ -102,19 +109,31 @@ void InitSave() { save_data_initialized = true; } - // initializer for save data -save_data_list_t::save_data_list_t(const char *name_in, save_data_tag_t tag_in, const void *ptr_in) : +/* +============= +save_data_list_t::save_data_list_t(const char *name_in, save_data_tag_t tag_in, const void *ptr_in, bool link, bool valid_in) : name(name_in), tag(tag_in), - ptr(ptr_in) { - if (save_data_initialized) + ptr(ptr_in), + next(nullptr), + valid(valid_in) { + if (save_data_initialized && link) gi.Com_Error("attempted to create save_data_list at runtime"); - next = list_head; - list_head = this; + if (link) { + next = list_head; + list_head = this; + } } +/* +============= +save_data_list_t::fetch + +Fetches a save data entry for a pointer/tag pair, returning a sentinel when missing. +============= +*/ const save_data_list_t *save_data_list_t::fetch(const void *ptr, save_data_tag_t tag) { auto link = list_from_ptr_hash.find(std::make_tuple(ptr, tag)); @@ -129,7 +148,12 @@ const save_data_list_t *save_data_list_t::fetch(const void *ptr, save_data_tag_t else gi.Com_PrintFmt("value pointer {} was not linked to save tag {}\n", ptr, (int32_t)tag); - return nullptr; + static save_data_list_t invalid_save_data("", SAVE_DATA_MMOVE, nullptr, false, false); + + invalid_save_data.tag = tag; + invalid_save_data.ptr = ptr; + + return &invalid_save_data; } std::string json_error_stack; diff --git a/src/g_save_test.cpp b/src/g_save_test.cpp new file mode 100644 index 0000000..73ae97d --- /dev/null +++ b/src/g_save_test.cpp @@ -0,0 +1,89 @@ +#include +#include + +#include "g_local.h" + +// Stub globals required by g_save.cpp +local_game_import_t gi{}; +g_fmt_data_t g_fmt_data{}; + +cvar_t strict_stub{ + const_cast("g_strict_saves"), + const_cast("0"), + nullptr, + static_cast(0), + 0, + 0.0f, + nullptr, + 0 +}; + +cvar_t deathmatch_stub{ + const_cast("deathmatch"), + const_cast("0"), + nullptr, + static_cast(0), + 0, + 0.0f, + nullptr, + 0 +}; + +cvar_t *g_strict_saves = &strict_stub; +cvar_t *deathmatch = &deathmatch_stub; + +/* +============= +StubComPrint + +Captures formatted error output for validation. +============= +*/ +static void StubComPrint(const char *message) { + (void)message; +} + +/* +============= +StubComError + +Captures fatal errors for validation. +============= +*/ +static void StubComError(const char *message) { + (void)message; +} + +#include "g_save.cpp" + +/* +============= +ValidateUnknownTagLookup + +Ensures save_data_list_t::fetch returns an invalid sentinel for unknown pointers. +============= +*/ +static void ValidateUnknownTagLookup() { + strict_stub.integer = 0; + deathmatch_stub.integer = 0; + gi.Com_Print = &StubComPrint; + gi.Com_Error = &StubComError; + + const void *unknown_ptr = reinterpret_cast(0x1234); + const save_data_list_t *result = save_data_list_t::fetch(unknown_ptr, SAVE_FUNC_THINK); + + assert(result != nullptr); + assert(!result->valid); + assert(result->ptr == unknown_ptr); + assert(result->tag == SAVE_FUNC_THINK); +} + +/* +============= +main +============= +*/ +int main() { + ValidateUnknownTagLookup(); + return 0; +} From e3280572a03be520f63aeca778e7908d93a4847d Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:25:07 +0000 Subject: [PATCH 041/142] Add doppelganger pickup limits --- src/g_items.cpp | 37 ++++++++++++++++++++------ src/g_items_limits.h | 18 +++++++++++++ src/g_local.h | 1 + src/g_main.cpp | 16 ++++++----- src/tests/pickup_doppelganger.test.cpp | 17 ++++++++++++ 5 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 src/g_items_limits.h create mode 100644 src/tests/pickup_doppelganger.test.cpp diff --git a/src/g_items.cpp b/src/g_items.cpp index d268c4a..5d06245 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -3,6 +3,7 @@ #include "g_local.h" #include "bots/bot_includes.h" #include "monsters/m_player.h" //doppelganger +#include "g_items_limits.h" bool Pickup_Weapon(gentity_t *ent, gentity_t *other); void Use_Weapon(gentity_t *ent, gitem_t *inv); @@ -1812,6 +1813,13 @@ static bool Pickup_Nuke(gentity_t *ent, gentity_t *other) { //====================================================================== +/* +============= +Use_Doppelganger + +Spawns a doppelganger at a nearby valid location and consumes the item. +============= +*/ static void Use_Doppelganger(gentity_t *ent, gitem_t *item) { vec3_t forward, right; vec3_t createPt, spawnPt; @@ -1835,15 +1843,26 @@ static void Use_Doppelganger(gentity_t *ent, gitem_t *item) { fire_doppelganger(ent, spawnPt, forward); } +/* +============= +Pickup_Doppelganger + +Checks for doppelganger limits, granting the pickup when allowed. +============= +*/ static bool Pickup_Doppelganger(gentity_t *ent, gentity_t *other) { - int quantity; + int quantity; + int max_allowed; if (!deathmatch->integer) return false; + max_allowed = G_GetHoldableMax(g_dm_holdable_doppel_max->integer, ent->item->quantity_warn, 1); quantity = other->client->pers.inventory[ent->item->id]; - if (quantity >= 1) // FIXME - apply max to doppelgangers + if (quantity >= max_allowed) { + gi.cprintf(other, PRINT_LOW, "You can't carry more %s\n", ent->item->pickup_name); return false; + } other->client->pers.inventory[ent->item->id]++; @@ -5053,12 +5072,14 @@ model="models/items/dopple/tris.md2" /* quantity */ 90, /* ammo */ IT_NULL, /* chain */ IT_NULL, - /* flags */ IF_TIMED | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_DOPPELGANGER, - /* precaches */ "models/objects/dopplebase/tris.md2 models/items/spawngro3/tris.md2 medic_commander/monsterspawn1.wav models/items/hunter/tris.md2 models/items/vengnce/tris.md2", - }, +/* flags */ IF_TIMED | IF_POWERUP_WHEEL, +/* vwep_model */ nullptr, +/* armor_info */ nullptr, +/* tag */ POWERUP_DOPPELGANGER, +/* precaches */ "models/objects/dopplebase/tris.md2 models/items/spawngro3/tris.md2 medic_commander/monsterspawn1.wav models/items/hunter/tris.md2 models/items/vengnce/tris.md2", +/* sort_id */ 0, +/* quantity_warn */ 1 +}, /* Tag Token */ { diff --git a/src/g_items_limits.h b/src/g_items_limits.h new file mode 100644 index 0000000..d61ec35 --- /dev/null +++ b/src/g_items_limits.h @@ -0,0 +1,18 @@ +#pragma once + +/* +============= +G_GetHoldableMax + +Returns the effective max for a holdable, preferring cvar overrides when set. +============= +*/ +constexpr int G_GetHoldableMax(int override_max, int item_max, int fallback) +{ + int max_value = override_max > 0 ? override_max : item_max; + return max_value > 0 ? max_value : fallback; +} + +static_assert(G_GetHoldableMax(3, 1, 1) == 3, "Override max should win when positive."); +static_assert(G_GetHoldableMax(0, 2, 1) == 2, "Item max should win when override is unset."); +static_assert(G_GetHoldableMax(0, 0, 1) == 1, "Fallback should apply when no max is defined."); diff --git a/src/g_local.h b/src/g_local.h index 211be5f..cd01149 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -2400,6 +2400,7 @@ extern cvar_t *g_dm_force_join; extern cvar_t *g_dm_force_respawn; extern cvar_t *g_dm_force_respawn_time; extern cvar_t *g_dm_holdable_adrenaline; +extern cvar_t *g_dm_holdable_doppel_max; extern cvar_t *g_dm_instant_items; extern cvar_t *g_dm_intermission_shots; extern cvar_t *g_dm_item_respawn_rate; diff --git a/src/g_main.cpp b/src/g_main.cpp index a72073c..9597be9 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -117,6 +117,7 @@ cvar_t *g_dm_force_join; cvar_t *g_dm_force_respawn; cvar_t *g_dm_force_respawn_time; cvar_t *g_dm_holdable_adrenaline; +cvar_t *g_dm_holdable_doppel_max; cvar_t *g_dm_instant_items; cvar_t *g_dm_intermission_shots; cvar_t *g_dm_item_respawn_rate; @@ -979,13 +980,14 @@ static void InitGame() { g_dm_do_readyup = gi.cvar("g_dm_do_readyup", "0", CVAR_NOFLAGS); g_dm_do_warmup = gi.cvar("g_dm_do_warmup", "1", CVAR_NOFLAGS); g_dm_exec_level_cfg = gi.cvar("g_dm_exec_level_cfg", "0", CVAR_NOFLAGS); - g_dm_force_join = gi.cvar("g_dm_force_join", "0", CVAR_NOFLAGS); - g_dm_force_respawn = gi.cvar("g_dm_force_respawn", "1", CVAR_NOFLAGS); - g_dm_force_respawn_time = gi.cvar("g_dm_force_respawn_time", "3", CVAR_NOFLAGS); - g_dm_holdable_adrenaline = gi.cvar("g_dm_holdable_adrenaline", "1", CVAR_NOFLAGS); - g_dm_instant_items = gi.cvar("g_dm_instant_items", "1", CVAR_NOFLAGS); - g_dm_intermission_shots = gi.cvar("g_dm_intermission_shots", "0", CVAR_NOFLAGS); - g_dm_item_respawn_rate = gi.cvar("g_dm_item_respawn_rate", "1.0", CVAR_NOFLAGS); +g_dm_force_join = gi.cvar("g_dm_force_join", "0", CVAR_NOFLAGS); +g_dm_force_respawn = gi.cvar("g_dm_force_respawn", "1", CVAR_NOFLAGS); +g_dm_force_respawn_time = gi.cvar("g_dm_force_respawn_time", "3", CVAR_NOFLAGS); +g_dm_holdable_adrenaline = gi.cvar("g_dm_holdable_adrenaline", "1", CVAR_NOFLAGS); +g_dm_holdable_doppel_max = gi.cvar("g_dm_holdable_doppel_max", "0", CVAR_NOFLAGS); +g_dm_instant_items = gi.cvar("g_dm_instant_items", "1", CVAR_NOFLAGS); +g_dm_intermission_shots = gi.cvar("g_dm_intermission_shots", "0", CVAR_NOFLAGS); +g_dm_item_respawn_rate = gi.cvar("g_dm_item_respawn_rate", "1.0", CVAR_NOFLAGS); g_dm_no_fall_damage = gi.cvar("g_dm_no_fall_damage", "0", CVAR_NOFLAGS); g_dm_no_quad_drop = gi.cvar("g_dm_no_quad_drop", "0", CVAR_NOFLAGS); g_dm_no_self_damage = gi.cvar("g_dm_no_self_damage", "0", CVAR_NOFLAGS); diff --git a/src/tests/pickup_doppelganger.test.cpp b/src/tests/pickup_doppelganger.test.cpp new file mode 100644 index 0000000..d36fa16 --- /dev/null +++ b/src/tests/pickup_doppelganger.test.cpp @@ -0,0 +1,17 @@ +#include "g_items_limits.h" +#include + +/* +============= +main + +Ensures doppelganger max selection honors override and item defaults. +============= +*/ +int main() +{ + assert(G_GetHoldableMax(2, 1, 1) == 2); + assert(G_GetHoldableMax(0, 3, 1) == 3); + assert(G_GetHoldableMax(0, 0, 1) == 1); + return 0; +} From 114b77ceb8bfebec2a795e2910f92d8f807ea0b9 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:26:07 +0000 Subject: [PATCH 042/142] Fix stuck object sorting range and add harness --- src/p_move.cpp | 96 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 3 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index 7437d3c..c53eb8d 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -6,7 +6,17 @@ #define GAME_INCLUDE #include "bg_local.h" -// [Paril-KEX] generic code to detect & fix a stuck object +#ifdef G_FIX_STUCK_OBJECT_GENERIC_TESTS +#include +#endif + +/* +============= +G_FixStuckObject_Generic + +Generic code to detect & fix a stuck object. +============= +*/ stuck_result_t G_FixStuckObject_Generic(vec3_t& origin, const vec3_t& own_mins, const vec3_t& own_maxs, std::function trace) { if (!trace(origin, own_mins, own_maxs, origin).startsolid) return stuck_result_t::GOOD_POSITION; @@ -134,16 +144,96 @@ stuck_result_t G_FixStuckObject_Generic(vec3_t& origin, const vec3_t& own_mins, } if (num_good_positions) { - std::sort(&good_positions[0], &good_positions[num_good_positions - 1], [](const auto& a, const auto& b) { return a.distance < b.distance; }); + std::sort(&good_positions[0], &good_positions[0] + num_good_positions, [](const auto& a, const auto& b) { return a.distance < b.distance; }); origin = good_positions[0].origin; return stuck_result_t::FIXED; - } +} return stuck_result_t::NO_GOOD_POSITION; } +#ifdef G_FIX_STUCK_OBJECT_GENERIC_TESTS +struct GFixStuckTestTraceState { + std::array offsets = { + vec3_t{ 0.0f, 0.0f, 4.0f }, + vec3_t{ 0.0f, 0.0f, 2.0f }, + vec3_t{ 0.0f, 0.0f, 1.0f }, + vec3_t{ 0.0f, 0.0f, 3.0f }, + vec3_t{ 0.0f, 0.0f, 5.0f }, + vec3_t{ 0.0f, 0.0f, 6.0f } +}; + size_t call = 0; +}; + +static GFixStuckTestTraceState g_fix_stuck_trace_state; + +/* +============= +G_FixStuck_TestTrace + +Provides deterministic trace responses for the G_FixStuckObject_Generic test harness. +============= +*/ +static trace_t G_FixStuck_TestTrace(const vec3_t& start, const vec3_t& mins, const vec3_t& maxs, const vec3_t& end) { + trace_t tr{}; + const size_t phase = g_fix_stuck_trace_state.call % 3; + const size_t side = g_fix_stuck_trace_state.call / 3; + + tr.startsolid = false; + tr.endpos = end; + + if (phase == 1 && side < g_fix_stuck_trace_state.offsets.size()) + tr.endpos = end + g_fix_stuck_trace_state.offsets[side]; + + g_fix_stuck_trace_state.call++; + return tr; +} + +/* +============= +Test_GFixStuckObject_SortingAndRecovery + +Verifies that G_FixStuckObject_Generic sorts candidate positions correctly and recovers the nearest option when multiple positions are available. +============= +*/ +static void Test_GFixStuckObject_SortingAndRecovery() { + g_fix_stuck_trace_state.call = 0; + vec3_t origin{ 0.0f, 0.0f, 0.0f }; + const vec3_t mins{ -1.0f, -1.0f, -1.0f }; + const vec3_t maxs{ 1.0f, 1.0f, 1.0f }; + + const stuck_result_t result = G_FixStuckObject_Generic(origin, mins, maxs, G_FixStuck_TestTrace); + + assert(result == stuck_result_t::FIXED); + assert(origin.equals(g_fix_stuck_trace_state.offsets[2])); +} + +/* +============= +Run_GFixStuckObject_Generic_Tests + +Runs the standalone harness for G_FixStuckObject_Generic. +============= +*/ +static void Run_GFixStuckObject_Generic_Tests() { + Test_GFixStuckObject_SortingAndRecovery(); +} + +#ifdef G_FIX_STUCK_OBJECT_GENERIC_TESTS_MAIN +/* +============= +main +============= +*/ +int main() { + Run_GFixStuckObject_Generic_Tests(); + return 0; +} +#endif +#endif + // all of the locals will be zeroed before each // pmove, just to make damn sure we don't have // any differences when running on client or server From 25fae1a383caf546fcd0b5f708bc536e35c86a7a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:26:45 +0000 Subject: [PATCH 043/142] Add CTF spawn initialization and checks --- src/g_spawn.cpp | 77 ++++++++++++++++++++++++++++++++---------------- src/p_client.cpp | 49 ++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 0dc5dd4..d715197 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -598,7 +598,7 @@ void ED_CallSpawn(gentity_t *ent) { if (GT(GT_BALL)) { ent->s.effects |= EF_COLOR_SHELL; ent->s.renderfx |= RF_SHELL_RED | RF_SHELL_GREEN; - } else { + } else { G_FreeEntity(ent); } return; @@ -1225,7 +1225,7 @@ static inline bool G_InhibitEntity(gentity_t *ent) { ((skill->integer >= 2) && ent->spawnflags.has(SPAWNFLAG_NOT_HARD)); } -void setup_shadow_lights(); +void setup_shadow_lights(); // [Paril-KEX] void PrecacheInventoryItems() { @@ -1600,7 +1600,7 @@ static void ParseWorldEntityString(const char *mapname, bool try_q3) { //gi.Com_PrintFmt("Entities override: \"{}\"\n", name); } } - } else { + } else { gi.Com_PrintFmt("{}: Entities override file load error for \"{}\", discarding.\n", __FUNCTION__, name); } } @@ -1615,7 +1615,7 @@ static void ParseWorldEntityString(const char *mapname, bool try_q3) { gi.Com_PrintFmt("{}: Entities override file written to: \"{}\"\n", __FUNCTION__, name); fclose(f); } - } else { + } else { if (g_verbose->integer) gi.Com_PrintFmt("{}: Entities override file not saved as file already exists: \"{}\"\n", __FUNCTION__, name); } @@ -1712,6 +1712,31 @@ static void ResetLevelState() { level.health_bar_entities.fill(nullptr); } +/* +============= +G_TestCTFSpawnPoints + +Ensure Capture the Flag spawn points remain linked and usable after +entity parsing. +============= +*/ +static void G_TestCTFSpawnPoints() { + if (!GTF(GTF_CTF)) + return; + + auto ensure_linked = [](const char *classname) { + gentity_t *spot = nullptr; + + while ((spot = G_FindByString<&gentity_t::classname>(spot, classname)) != nullptr) { + if (!spot->linkcount) + gi.linkentity(spot); + } + }; + + ensure_linked("info_player_team_red"); + ensure_linked("info_player_team_blue"); +} + /* ============== SpawnEntities @@ -1773,7 +1798,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp gi.Com_PrintFmt("{}: Entities override file verified and loaded: \"{}\"\n", __FUNCTION__, name); } } - } else { + } else { gi.Com_PrintFmt("{}: Entities override file load error for \"{}\", discarding.\n", __FUNCTION__, name); } } @@ -1788,7 +1813,7 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp gi.Com_PrintFmt("{}: Entities override file written to: \"{}\"\n", __FUNCTION__, name); fclose(f); } - } else { + } else { //gi.Com_PrintFmt("{}: Entities override file not saved as file already exists: \"{}\"\n", __FUNCTION__, name); } } @@ -1851,6 +1876,8 @@ globals.num_entities = game.maxclients + 1; QuadHog_SetupSpawn(5_sec); Tech_SetupSpawn(); + G_TestCTFSpawnPoints(); + if (deathmatch->integer) { if (g_dm_random_items->integer) PrecacheForRandomRespawn(); @@ -1863,7 +1890,7 @@ globals.num_entities = game.maxclients + 1; game.item_inhibit_wp = 0; } else { InitHintPaths(); // if there aren't hintpaths on this map, enable quick aborts - } +} G_LocateSpawnSpots(); @@ -1964,7 +1991,7 @@ static void G_InitStatusbar() { sb.ifstat(STAT_HEALTH_BARS).yt(24).health_bars().endifstat(); sb.story(); - } else { + } else { if (Teams()) { // flag carrier indicator if (GTF(GTF_CTF)) @@ -2006,7 +2033,7 @@ static void G_InitStatusbar() { void GT_SetLongName(void) { const char *s; - if (deathmatch->integer) { + if (deathmatch->integer) { if (GT(GT_CTF)) { if (g_instagib->integer) { s = "Insta-CTF"; @@ -2018,7 +2045,7 @@ void GT_SetLongName(void) { s = "NadeFest CTF"; } else if (g_quadhog->integer) { s = "Quad Hog CTF"; - } else { + } else { s = gt_long_name[GT_CTF]; } } else if (GT(GT_FREEZE)) { @@ -2032,7 +2059,7 @@ void GT_SetLongName(void) { s = "NadeFest Freeze"; } else if (g_quadhog->integer) { s = "Quad Hog Freeze"; - } else { + } else { s = gt_long_name[GT_FREEZE]; } } else if (GT(GT_CA)) { @@ -2046,7 +2073,7 @@ void GT_SetLongName(void) { s = "NadeFest CA"; } else if (g_quadhog->integer) { s = "Quad Hog CA"; - } else { + } else { s = gt_long_name[GT_CA]; } } else if (GT(GT_RR)) { @@ -2060,7 +2087,7 @@ void GT_SetLongName(void) { s = "NadeFest RR"; } else if (g_quadhog->integer) { s = "Quad Hog RR"; - } else { + } else { s = gt_long_name[GT_RR]; } } else if (GT(GT_STRIKE)) { @@ -2074,7 +2101,7 @@ void GT_SetLongName(void) { s = "NadeFest Strike"; } else if (g_quadhog->integer) { s = "Quad Hog Strike"; - } else { + } else { s = gt_long_name[GT_STRIKE]; } } else if (GT(GT_TDM)) { @@ -2088,7 +2115,7 @@ void GT_SetLongName(void) { s = "NadeFest TDM"; } else if (g_quadhog->integer) { s = "Quad Hog TDM"; - } else { + } else { s = gt_long_name[GT_TDM]; } } else if (GT(GT_DUEL)) { @@ -2102,7 +2129,7 @@ void GT_SetLongName(void) { s = "NadeFest Duel"; } else if (g_quadhog->integer) { s = "Quad Hog Duel"; - } else { + } else { s = gt_long_name[GT_DUEL]; } } else if (GT(GT_HORDE)) { @@ -2116,7 +2143,7 @@ void GT_SetLongName(void) { s = "NadeFest Horde"; } else if (g_quadhog->integer) { s = "Quad Hog Horde"; - } else { + } else { s = gt_long_name[GT_HORDE]; } } else if (GT(GT_BALL)) { @@ -2130,10 +2157,10 @@ void GT_SetLongName(void) { s = "NadeFest ProBall"; } else if (g_quadhog->integer) { s = "Quad Hog ProBall"; - } else { + } else { s = gt_long_name[GT_BALL]; } - } else if (deathmatch->integer) { + } else if (deathmatch->integer) { if (g_instagib->integer) { s = "InstaGib"; } else if (g_vampiric_damage->integer) { @@ -2144,16 +2171,16 @@ void GT_SetLongName(void) { s = "NadeFest"; } else if (g_quadhog->integer) { s = "Quad Hog"; - } else { + } else { s = gt_long_name[GT_FFA]; } - } else { + } else { s = "Unknown Gametype"; } - } else { + } else { if (coop->integer) { s = "Co-op"; - } else { + } else { s = "Single Player"; } } @@ -2245,7 +2272,7 @@ void SP_worldspawn(gentity_t *ent) { if (st.music && st.music[0]) { gi.configstring(CS_CDTRACK, st.music); - } else { + } else { gi.configstring(CS_CDTRACK, G_Fmt("{}", ent->sounds).data()); } @@ -2304,7 +2331,7 @@ void SP_worldspawn(gentity_t *ent) { if (!st.gravity) { level.gravity = 800.f; gi.cvar_set("g_gravity", "800"); - } else { + } else { level.gravity = atof(st.gravity); gi.cvar_set("g_gravity", st.gravity); } diff --git a/src/p_client.cpp b/src/p_client.cpp index f1ebd45..18c51c6 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -61,15 +61,60 @@ void SP_info_player_deathmatch(gentity_t* self) { deathmatch_spawn_flags(self); } +/* +============= +CTF_TeamSpawnSetup + +Initialize CTF team spawn points with deathmatch gating, stuck resolution +and spawnpad linking. +============= +*/ +static void CTF_TeamSpawnSetup(gentity_t* self) { + if (!deathmatch->integer) { + G_FreeEntity(self); + return; + } + + if (gi.trace(self->s.origin, PLAYER_MINS, PLAYER_MAXS, self->s.origin, self, MASK_SOLID).startsolid) + G_FixStuckObject(self, self->s.origin); + + if (level.is_n64) { + self->think = info_player_start_drop; + self->nextthink = level.time + FRAME_TIME_S; + } + + SP_misc_teleporter_dest(self); + + deathmatch_spawn_flags(self); +} + /*QUAKED info_player_team_red (1 0 0) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP A potential Red Team spawning position for CTF games. */ -void SP_info_player_team_red(gentity_t* self) {} +/* +============= +SP_info_player_team_red + +Set up red team CTF spawn points. +============= +*/ +void SP_info_player_team_red(gentity_t* self) { + CTF_TeamSpawnSetup(self); +} /*QUAKED info_player_team_blue (0 0 1) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP A potential Blue Team spawning position for CTF games. */ -void SP_info_player_team_blue(gentity_t* self) {} +/* +============= +SP_info_player_team_blue + +Set up blue team CTF spawn points. +============= +*/ +void SP_info_player_team_blue(gentity_t* self) { + CTF_TeamSpawnSetup(self); +} /*QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) x x x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP A potential spawning position for coop games. From 32455f392d9ed307ea2ad1f773ea6430b667b38a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:27:19 +0000 Subject: [PATCH 044/142] Preserve intermission scoreboard visibility --- docs/intermission_flow_test.md | 17 +++++++++++++++++ src/p_hud.cpp | 11 ++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 docs/intermission_flow_test.md diff --git a/docs/intermission_flow_test.md b/docs/intermission_flow_test.md new file mode 100644 index 0000000..9199ef1 --- /dev/null +++ b/docs/intermission_flow_test.md @@ -0,0 +1,17 @@ +# Intermission Flow Test: Scoreboard Persistence + +## Objective +Confirm that deathmatch players remain on the scoreboard view throughout intermission instead of being forced back to other HUD layouts. + +## Preconditions +- Start a multiplayer deathmatch session with at least one human or bot participant. +- Reach a state where fraglimit or timelimit is close to completion so intermission can be triggered quickly. + +## Steps +1. Play until the match ends naturally (or use an admin command to end the match) to enter intermission. +2. Observe the client HUD as intermission begins. +3. Remain idle during the entire intermission countdown without pressing the score key or toggling menus. + +## Expected Result +- The scoreboard remains visible for the entire intermission without reverting to another HUD state. +- Player input is not required to keep the scoreboard displayed during intermission. diff --git a/src/p_hud.cpp b/src/p_hud.cpp index f85e64e..867fb3e 100644 --- a/src/p_hud.cpp +++ b/src/p_hud.cpp @@ -26,6 +26,13 @@ static const char *EndMatchVictorString() { void MultiplayerScoreboard(gentity_t *ent); +/* +============= +MoveClientToIntermission + +Move a client into the intermission state and set HUD visibility. +============= +*/ void MoveClientToIntermission(gentity_t *ent) { // [Paril-KEX] if (ent->client->ps.pmove.pm_type != PM_FREEZE) @@ -57,7 +64,9 @@ void MoveClientToIntermission(gentity_t *ent) { ent->client->grenade_time = 0_ms; ent->client->showhelp = false; - ent->client->showscores = false; + + if (!deathmatch->integer) + ent->client->showscores = false; globals.server_flags &= ~SERVER_FLAG_SLOW_TIME; From f316a62d025fb373398a9c62ee4604fb7445ba80 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:27:56 +0000 Subject: [PATCH 045/142] Implement victor string builder and HUD tests --- src/p_hud.cpp | 61 ++++++++++++++++++++++------ src/p_hud_victor.cpp | 47 +++++++++++++++++++++ src/p_hud_victor.h | 30 ++++++++++++++ src/p_hud_victor_tests.cpp | 83 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 src/p_hud_victor.cpp create mode 100644 src/p_hud_victor.h create mode 100644 src/p_hud_victor_tests.cpp diff --git a/src/p_hud.cpp b/src/p_hud.cpp index f85e64e..05d98ec 100644 --- a/src/p_hud.cpp +++ b/src/p_hud.cpp @@ -2,6 +2,7 @@ // Licensed under the GNU General Public License 2.0. #include "g_local.h" #include "g_statusbar.h" +#include "p_hud_victor.h" /* ====================================================================== @@ -11,17 +12,44 @@ INTERMISSION ====================================================================== */ +/* +============= +EndMatchVictorString + +Determines the intermission victor string for the current match and copies it into the level buffer. +============= +*/ static const char *EndMatchVictorString() { if (!level.intermission_time) return nullptr; - const char *s = nullptr; + intermission_victor_context_t context{}; + context.intermission_active = true; + context.existing_message = level.intermission_victor_msg[0] ? level.intermission_victor_msg : nullptr; - if (Teams() && !(GT(GT_RR))) { - - return s; + context.teams = Teams() && !(GT(GT_RR)); + + if (context.teams) { + context.red_score = level.team_scores[TEAM_RED]; + context.blue_score = level.team_scores[TEAM_BLUE]; + context.red_name = Teams_TeamName(TEAM_RED); + context.blue_name = Teams_TeamName(TEAM_BLUE); + } else { + if (level.sorted_clients[0] >= 0) { + gclient_t *leader = &game.clients[level.sorted_clients[0]]; + context.ffa_winner_name = leader->resp.netname; + context.ffa_winner_score = leader->resp.score; + } + + if (level.sorted_clients[1] >= 0) { + gclient_t *runner = &game.clients[level.sorted_clients[1]]; + context.ffa_runner_up_present = true; + context.ffa_runner_up_score = runner->resp.score; + } } + const char *victor = BuildIntermissionVictorString(context, level.intermission_victor_msg, sizeof(level.intermission_victor_msg)); + return victor ? victor : nullptr; } void MultiplayerScoreboard(gentity_t *ent); @@ -322,6 +350,8 @@ void TeamsScoreboardMessage(gentity_t *ent, gentity_t *killer) { fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -30 cstring2 \"Score Limit: {}\" "), GT_ScoreLimit()); if (level.intermission_time) { + EndMatchVictorString(); + //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -50 cstring2 \"{} - {}\" "), level.gamemod_name, level.gametype_name); //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -40 cstring2 \"[{}] {}\" "), level.mapname, level.level_name); if (level.match_start_time) { @@ -345,7 +375,7 @@ void TeamsScoreboardMessage(gentity_t *ent, gentity_t *killer) { */ if (timelimit->value && !level.intermission_time) { //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 0 + #if 0 //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 {} "), gi.ServerFrame() + level.time.milliseconds() / gi.frame_time_ms); int32_t val = gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms; const char *s; @@ -354,7 +384,7 @@ void TeamsScoreboardMessage(gentity_t *ent, gentity_t *killer) { s = G_Fmt("{:02}:{:02}", (remaining_ms / 1000) / 60, (remaining_ms / 1000) % 60).data(); fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 \"{}\" "), s); -#endif + #endif } fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -48 cstring2 \"{}\" "), "Use inventory bind to toggle menu."); @@ -531,6 +561,8 @@ static void DuelScoreboardMessage(gentity_t *ent, gentity_t *killer) { fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -30 cstring2 \"Score Limit: {}\" "), GT_ScoreLimit()); if (level.intermission_time) { + EndMatchVictorString(); + if (level.match_start_time) { int t = (level.intermission_time - level.match_start_time - 1_sec).milliseconds(); fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -50 cstring2 \"Total Match Time: {}\" "), G_TimeStringMs(t, false)); @@ -732,6 +764,8 @@ static inline void ScoreboardNotice(gentity_t *ent, std::string string) { fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -30 cstring2 \"Score Limit: {}\" "), GT_ScoreLimit()); if (level.intermission_time) { + EndMatchVictorString(); + //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -50 cstring2 \"{} - {}\" "), level.gamemod_name, level.gametype_name); //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -40 cstring2 \"[{}] {}\" "), level.mapname, level.level_name); if (level.match_start_time) { @@ -752,11 +786,11 @@ static inline void ScoreboardNotice(gentity_t *ent, std::string string) { /* else if (GT(GT_HORDE) && level.round_number > 0) fmt::format_to(std::back_inserter(string), FMT_STRING("xv -20 yv -10 loc_string2 1 Wave: \"{}\" "), level.round_number); - */ + */ if (timelimit->value && !level.intermission_time) { //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 0 - //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 {} "), gi.ServerFrame() + level.time.milliseconds() / gi.frame_time_ms); + #if 0 + //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 {} "), gi.ServerFrame() + level.time.milliseconds() / gi.frame_time_ms); int32_t val = gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms; const char *s; int32_t remaining_ms = gtime_t::from_ms(level.time); // (val - gi.ServerFrame()) *gi.frame_time_ms; @@ -764,13 +798,14 @@ static inline void ScoreboardNotice(gentity_t *ent, std::string string) { s = G_Fmt("{:02}:{:02}", (remaining_ms / 1000) / 60, (remaining_ms / 1000) % 60).data(); fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 \"{}\" "), s); -#endif + #endif } fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -48 cstring2 \"{}\" "), "Use inventory bind to toggle menu."); } } + /* ================== DeathmatchScoreboardMessage @@ -851,6 +886,8 @@ void DeathmatchScoreboardMessage(gentity_t *ent, gentity_t *killer) { fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -30 cstring2 \"Score Limit: {}\" "), GT_ScoreLimit()); if (level.intermission_time) { + EndMatchVictorString(); + //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -50 cstring2 \"{} - {}\" "), level.gamemod_name, level.gametype_name); //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yv -40 cstring2 \"[{}] {}\" "), level.mapname, level.level_name); if (level.match_start_time) { @@ -875,7 +912,7 @@ void DeathmatchScoreboardMessage(gentity_t *ent, gentity_t *killer) { */ if (timelimit->value && !level.intermission_time) { //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 0 + #if 0 //fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 {} "), gi.ServerFrame() + level.time.milliseconds() / gi.frame_time_ms); int32_t val = gi.ServerFrame() + ((gtime_t::from_min(timelimit->value) - level.time)).milliseconds() / gi.frame_time_ms; const char *s; @@ -884,7 +921,7 @@ void DeathmatchScoreboardMessage(gentity_t *ent, gentity_t *killer) { s = G_Fmt("{:02}:{:02}", (remaining_ms / 1000) / 60, (remaining_ms / 1000) % 60).data(); fmt::format_to(std::back_inserter(string), FMT_STRING("xv 340 yv -10 loc_string2 1 \"{}\" "), s); -#endif + #endif } fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -48 cstring2 \"{}\" "), "Use inventory bind to toggle menu."); diff --git a/src/p_hud_victor.cpp b/src/p_hud_victor.cpp new file mode 100644 index 0000000..707c143 --- /dev/null +++ b/src/p_hud_victor.cpp @@ -0,0 +1,47 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "p_hud_victor.h" + +#include + +/* +============= +BuildIntermissionVictorString + +Populates the provided buffer with the appropriate victor string for the current match context. +Returns either the buffer pointer or nullptr if no message is generated. +============= +*/ +const char *BuildIntermissionVictorString(const intermission_victor_context_t &context, char *buffer, size_t buffer_size) { + if (!buffer || !buffer_size) + return nullptr; + + buffer[0] = '\0'; + + if (!context.intermission_active) + return nullptr; + + if (context.existing_message && context.existing_message[0]) { + std::snprintf(buffer, buffer_size, "%s", context.existing_message); + return buffer; + } + + if (context.teams) { + if (context.red_score > context.blue_score && context.red_name) { + std::snprintf(buffer, buffer_size, "%s WINS with a final score of %d to %d.", context.red_name, context.red_score, context.blue_score); + } else if (context.blue_score > context.red_score && context.blue_name) { + std::snprintf(buffer, buffer_size, "%s WINS with a final score of %d to %d.", context.blue_name, context.blue_score, context.red_score); + } else if (context.red_name && context.blue_name) { + std::snprintf(buffer, buffer_size, "Match is a tie: %d to %d.", context.red_score, context.blue_score); + } + } else if (context.ffa_winner_name) { + if (context.ffa_runner_up_present && context.ffa_runner_up_score == context.ffa_winner_score) { + std::snprintf(buffer, buffer_size, "Match ended in a tie at %d.", context.ffa_winner_score); + } else { + std::snprintf(buffer, buffer_size, "%s WINS with a final score of %d.", context.ffa_winner_name, context.ffa_winner_score); + } + } + + return buffer[0] ? buffer : nullptr; +} diff --git a/src/p_hud_victor.h b/src/p_hud_victor.h new file mode 100644 index 0000000..b510a9d --- /dev/null +++ b/src/p_hud_victor.h @@ -0,0 +1,30 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +#include + +struct intermission_victor_context_t { + bool intermission_active{}; + const char *existing_message{}; + bool teams{}; + int red_score{}; + int blue_score{}; + const char *red_name{}; + const char *blue_name{}; + const char *ffa_winner_name{}; + int ffa_winner_score{}; + bool ffa_runner_up_present{}; + int ffa_runner_up_score{}; +}; + +/* +============= +BuildIntermissionVictorString + +Populates the provided buffer with the appropriate victor string for the current match context. +Returns either the buffer pointer or nullptr if no message is generated. +============= +*/ +const char *BuildIntermissionVictorString(const intermission_victor_context_t &context, char *buffer, size_t buffer_size); diff --git a/src/p_hud_victor_tests.cpp b/src/p_hud_victor_tests.cpp new file mode 100644 index 0000000..f4585b6 --- /dev/null +++ b/src/p_hud_victor_tests.cpp @@ -0,0 +1,83 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "p_hud_victor.h" + +#include +#include + +/* +============= +TestTeamVictor + +Verifies that team victories report the correct winning name and score spread. +============= +*/ +static void TestTeamVictor() { + intermission_victor_context_t context{}; + context.intermission_active = true; + context.teams = true; + context.red_name = "Red"; + context.blue_name = "Blue"; + context.red_score = 25; + context.blue_score = 10; + + char buffer[64]; + const char *result = BuildIntermissionVictorString(context, buffer, sizeof(buffer)); + assert(result); + assert(std::strcmp(result, "Red WINS with a final score of 25 to 10.") == 0); +} + +/* +============= +TestFFAVictor + +Ensures FFA results prefer the top scorer when there is no tie. +============= +*/ +static void TestFFAVictor() { + intermission_victor_context_t context{}; + context.intermission_active = true; + context.ffa_winner_name = "PlayerOne"; + context.ffa_winner_score = 15; + context.ffa_runner_up_present = true; + context.ffa_runner_up_score = 10; + + char buffer[64]; + const char *result = BuildIntermissionVictorString(context, buffer, sizeof(buffer)); + assert(result); + assert(std::strcmp(result, "PlayerOne WINS with a final score of 15.") == 0); +} + +/* +============= +TestFFATie + +Confirms that ties in FFA emit a tie-specific victor string. +============= +*/ +static void TestFFATie() { + intermission_victor_context_t context{}; + context.intermission_active = true; + context.ffa_winner_name = "PlayerOne"; + context.ffa_winner_score = 12; + context.ffa_runner_up_present = true; + context.ffa_runner_up_score = 12; + + char buffer[64]; + const char *result = BuildIntermissionVictorString(context, buffer, sizeof(buffer)); + assert(result); + assert(std::strcmp(result, "Match ended in a tie at 12.") == 0); +} + +/* +============= +main +============= +*/ +int main() { + TestTeamVictor(); + TestFFAVictor(); + TestFFATie(); + return 0; +} From c60a5c65624a1706894a30fea04e76055bc91747 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:28:27 +0000 Subject: [PATCH 046/142] Handle COM_ParseEx buffer edge cases --- src/q_std.cpp | 87 ++++++++++++++++------------ src/q_std.h | 14 +++-- src/q_std_parsing_tests.cpp | 110 ++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 src/q_std_parsing_tests.cpp diff --git a/src/q_std.cpp b/src/q_std.cpp index 87c959f..1ff8792 100644 --- a/src/q_std.cpp +++ b/src/q_std.cpp @@ -28,22 +28,35 @@ COM_ParseEx Parse a token out of a string ============== */ -char *COM_ParseEx(const char **data_p, const char *seps, char *buffer, size_t buffer_size) +char *COM_ParseEx(const char **data_p, const char *seps, char *buffer, size_t buffer_size, bool *overflowed) { static char com_token[MAX_TOKEN_CHARS]; + bool overflow_flag = false; + + if (overflowed) + *overflowed = false; + if (!buffer) { buffer = com_token; buffer_size = MAX_TOKEN_CHARS; } + else if (!buffer_size) + { + overflow_flag = true; + buffer = com_token; + buffer_size = MAX_TOKEN_CHARS; + } - int c; - int len; + int c; + size_t len; + size_t stored_len; const char *data; data = *data_p; len = 0; + stored_len = 0; buffer[0] = '\0'; if (!data) @@ -56,19 +69,19 @@ char *COM_ParseEx(const char **data_p, const char *seps, char *buffer, size_t bu skipwhite: while (COM_IsSeparator(c = *data, seps)) { - if (c == '\0') - { - *data_p = nullptr; - return buffer; - } - data++; + if (c == '\0') + { + *data_p = nullptr; + return buffer; + } + data++; } // skip // comments if (c == '/' && data[1] == '/') { while (*data && *data != '\n') - data++; + data++; goto skipwhite; } @@ -78,45 +91,49 @@ char *COM_ParseEx(const char **data_p, const char *seps, char *buffer, size_t bu data++; while (1) { - c = *data++; - if (c == '\"' || !c) - { - const size_t endpos = std::min(len, buffer_size - 1); // [KEX] avoid overflow - buffer[endpos] = '\0'; - *data_p = data; - return buffer; - } - if (len < buffer_size) - { - buffer[len] = c; + c = *data++; + if (c == '\"' || !c) + { + const bool token_overflowed = len >= buffer_size; + overflow_flag = overflow_flag || token_overflowed; + buffer[stored_len] = '\0'; + *data_p = data; + if (overflowed) + *overflowed = overflow_flag; + return buffer; + } + if (stored_len + 1 < buffer_size) + { + buffer[stored_len++] = c; + } len++; - } } } // parse a regular word do { - if (len < buffer_size) - { - buffer[len] = c; + if (stored_len + 1 < buffer_size) + { + buffer[stored_len++] = c; + } len++; - } - data++; - c = *data; + data++; + c = *data; } while (!COM_IsSeparator(c, seps)); - if (len == buffer_size) - { - gi.Com_PrintFmt("Token exceeded {} chars, discarded.\n", buffer_size); - len = 0; - } - buffer[len] = '\0'; + const bool token_overflowed = len >= buffer_size; + overflow_flag = overflow_flag || token_overflowed; + buffer[stored_len] = '\0'; + + if (token_overflowed) + gi.Com_PrintFmt("Token exceeded {} chars, truncated.\n", buffer_size); + if (overflowed) + *overflowed = overflow_flag; *data_p = data; return buffer; } - /* ============================================================================ diff --git a/src/q_std.h b/src/q_std.h index 8024c23..618a0ad 100644 --- a/src/q_std.h +++ b/src/q_std.h @@ -199,12 +199,18 @@ LerpAngle //============================================= -char *COM_ParseEx(const char **data_p, const char *seps, char *buffer = nullptr, size_t buffer_size = 0); +char *COM_ParseEx(const char **data_p, const char *seps, char *buffer = nullptr, size_t buffer_size = 0, bool *overflowed = nullptr); -// data is an in/out parm, returns a parsed out token -inline char *COM_Parse(const char **data_p, char *buffer = nullptr, size_t buffer_size = 0) +/* +============= +COM_Parse + +data is an in/out parm, returns a parsed out token +============= +*/ +inline char *COM_Parse(const char **data_p, char *buffer = nullptr, size_t buffer_size = 0, bool *overflowed = nullptr) { - return COM_ParseEx(data_p, "\r\n\t ", buffer, buffer_size); + return COM_ParseEx(data_p, "\r\n\t ", buffer, buffer_size, overflowed); } //============================================= diff --git a/src/q_std_parsing_tests.cpp b/src/q_std_parsing_tests.cpp new file mode 100644 index 0000000..f379276 --- /dev/null +++ b/src/q_std_parsing_tests.cpp @@ -0,0 +1,110 @@ +#include "q_std.cpp" + +#include +#include +#include +#include + +local_game_import_t gi{}; +char local_game_import_t::print_buffer[0x10000]; +static std::vector g_print_buffer; + +/* +============= +DummyComPrint + +Captures formatted output for verification without relying on the engine. +============= +*/ +static void DummyComPrint(const char *msg) +{ + if (msg) + g_print_buffer.emplace_back(msg); +} + +/* +============= +ResetPrintBuffer +============= +*/ +static void ResetPrintBuffer() +{ + g_print_buffer.clear(); +} + +/* +============= +TestZeroLengthBufferGuard + +Ensures zero-length buffers are guarded and parsing still returns a token. +============= +*/ +static void TestZeroLengthBufferGuard() +{ + const char *data = "token"; + const char *cursor = data; + char buffer[] = "Z"; + bool overflowed = false; + ResetPrintBuffer(); + char *token = COM_ParseEx(&cursor, "\r\n ", buffer, 0, &overflowed); + assert(token != nullptr); + assert(overflowed); + assert(std::strcmp(token, "token") == 0); + assert(buffer[0] == 'Z'); +} + +/* +============= +TestOversizedTokenFlag + +Confirms oversized tokens set the overflow flag and truncate instead of clearing. +============= +*/ +static void TestOversizedTokenFlag() +{ + const char *data = "oversize"; + const char *cursor = data; + char buffer[5]; + bool overflowed = false; + ResetPrintBuffer(); + char *token = COM_ParseEx(&cursor, "\r\n ", buffer, sizeof(buffer), &overflowed); + assert(token != nullptr); + assert(overflowed); + assert(std::strcmp(token, "over") == 0); + assert(!g_print_buffer.empty()); +} + +/* +============= +TestExactFitToken + +Verifies tokens that fit exactly within the buffer size do not trigger overflow handling. +============= +*/ +static void TestExactFitToken() +{ + const char *data = "fits"; + const char *cursor = data; + char buffer[5]; + bool overflowed = false; + ResetPrintBuffer(); + char *token = COM_ParseEx(&cursor, "\r\n ", buffer, sizeof(buffer), &overflowed); + assert(token != nullptr); + assert(!overflowed); + assert(std::strcmp(token, "fits") == 0); + assert(g_print_buffer.empty()); +} + +/* +============= +main +============= +*/ +int main() +{ + gi.Com_Print = DummyComPrint; + TestZeroLengthBufferGuard(); + TestOversizedTokenFlag(); + TestExactFitToken(); + return 0; +} From 01adf28256e0b66909e2bf1cdb810dcaccca2671 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:29:05 +0000 Subject: [PATCH 047/142] Make pain animation cycle per-client --- src/g_local.h | 3 ++- src/p_view.cpp | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 211be5f..ee10192 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -3592,6 +3592,7 @@ struct gclient_t { bool anim_duck; bool anim_run; gtime_t anim_time; + int32_t pain_anim_index; // powerup timers gtime_t pu_time_quad; @@ -4350,4 +4351,4 @@ template<> cached_modelindex *cached_modelindex::head; template<> cached_imageindex *cached_imageindex::head; extern cached_modelindex sm_meat_index; -extern cached_soundindex snd_fry; \ No newline at end of file +extern cached_soundindex snd_fry; diff --git a/src/p_view.cpp b/src/p_view.cpp index b5ad66e..8cc5e1e 100644 --- a/src/p_view.cpp +++ b/src/p_view.cpp @@ -106,15 +106,15 @@ void P_DamageFeedback(gentity_t *player) { // start a pain animation if still in the player model if (client->anim_priority < ANIM_PAIN && player->s.modelindex == MODELINDEX_PLAYER) { - static int i; + int &pain_anim_index = client->pain_anim_index; client->anim_priority = ANIM_PAIN; if (client->ps.pmove.pm_flags & PMF_DUCKED) { player->s.frame = FRAME_crpain1 - 1; client->anim_end = FRAME_crpain4; } else { - i = (i + 1) % 3; - switch (i) { + pain_anim_index = (pain_anim_index + 1) % 3; + switch (pain_anim_index) { case 0: player->s.frame = FRAME_pain101 - 1; client->anim_end = FRAME_pain104; @@ -132,7 +132,6 @@ void P_DamageFeedback(gentity_t *player) { client->anim_time = 0_ms; } - realcount = count; // if we took health damage, do a minimum clamp @@ -1483,4 +1482,4 @@ void ClientEndServerFrame(gentity_t *ent) { if (!clipped_player) ent->clipmask |= CONTENTS_PLAYER; } -} \ No newline at end of file +} From 08016f0e3e94ab5ed23b6ebd00be616f93d3cfc1 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:29:34 +0000 Subject: [PATCH 048/142] Guard Cmd_Give_f argc usage --- src/g_cmds.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 54ffdb6..bab8972 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -199,7 +199,8 @@ Give items to a client ================== */ static void Cmd_Give_f(gentity_t *ent) { - const char *name = gi.args(); + int argc = gi.argc(); + const char *name = (argc >= 2) ? gi.args() : ""; gitem_t *it; size_t i; bool give_all; @@ -210,8 +211,8 @@ static void Cmd_Give_f(gentity_t *ent) { else give_all = false; - if (give_all || Q_strcasecmp(gi.argv(1), "health") == 0) { - if (gi.argc() == 3) + if (give_all || (argc >= 2 && Q_strcasecmp(gi.argv(1), "health") == 0)) { + if (argc == 3) ent->health = atoi(gi.argv(2)); else ent->health = ent->max_health; @@ -299,7 +300,7 @@ static void Cmd_Give_f(gentity_t *ent) { } it = FindItem(name); - if (!it) { + if (!it && argc >= 2) { name = gi.argv(1); it = FindItem(name); } @@ -324,7 +325,7 @@ static void Cmd_Give_f(gentity_t *ent) { it_ent = G_Spawn(); it_ent->classname = it->classname; SpawnItem(it_ent, it); - if (it->flags & IF_AMMO && gi.argc() == 3) + if (it->flags & IF_AMMO && argc == 3) it_ent->count = atoi(gi.argv(2)); // since some items don't actually spawn when you say to .. From c1801e751ef5df2e191f19bdae3020a4eec09998 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:30:02 +0000 Subject: [PATCH 049/142] Preserve custom gravity during pushes --- docs/custom_gravity_tests.md | 8 ++++++++ src/g_phys.cpp | 5 +++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 docs/custom_gravity_tests.md diff --git a/docs/custom_gravity_tests.md b/docs/custom_gravity_tests.md new file mode 100644 index 0000000..a3e53f2 --- /dev/null +++ b/docs/custom_gravity_tests.md @@ -0,0 +1,8 @@ +# Custom Gravity Push Coverage + +A manual check to ensure custom gravity values survive push interactions: + +1. Spawn a movable entity (e.g., a crate) and set its `gravity` to a non-default value such as `0.5`. +2. Position the entity on top of a moving platform so it will be pushed during the simulation. +3. Run a push movement tick and confirm the entity's `gravity` remains `0.5` after the push completes. +4. Verify triggers along the movement path still fire for the pushed entity. diff --git a/src/g_phys.cpp b/src/g_phys.cpp index fe82964..56992d5 100644 --- a/src/g_phys.cpp +++ b/src/g_phys.cpp @@ -189,6 +189,7 @@ Does not change the entities velocity at all static trace_t G_PushEntity(gentity_t *ent, const vec3_t &push) { vec3_t start = ent->s.origin; vec3_t end = start + push; + float saved_gravity = ent->gravity; trace_t trace = gi.trace(start, ent->mins, ent->maxs, end, ent, G_GetClipMask(ent)); @@ -207,8 +208,8 @@ static trace_t G_PushEntity(gentity_t *ent, const vec3_t &push) { } } - // FIXME - is this needed? - ent->gravity = 1.0; + if (ent->gravity != saved_gravity) + ent->gravity = saved_gravity; if (ent->inuse) G_TouchTriggers(ent); From 8a166858eb25e2974ada84962944bf29f756fe00 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:30:45 +0000 Subject: [PATCH 050/142] Avoid duplicate trigger touches during pushes --- src/g_phys.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/g_phys.cpp b/src/g_phys.cpp index fe82964..31d61b5 100644 --- a/src/g_phys.cpp +++ b/src/g_phys.cpp @@ -367,9 +367,30 @@ static bool G_Push(gentity_t *pusher, vec3_t &move, vec3_t &amove) { } // FIXME: is there a better way to handle this? - // see if anything we moved has touched a trigger - for (p = pushed_p - 1; p >= pushed; p--) + // see if anything we moved has touched a trigger, but avoid + // invoking callbacks multiple times for the same entity in a + // single push. + gentity_t *touched[MAX_ENTITIES]; + uint32_t num_touched = 0; + + for (p = pushed_p - 1; p >= pushed; p--) { + bool already_touched = false; + + for (uint32_t i = 0; i < num_touched; i++) { + if (touched[i] == p->ent) { + already_touched = true; + break; + } + } + + if (already_touched) + continue; + + if (num_touched < lengthof(touched)) + touched[num_touched++] = p->ent; + G_TouchTriggers(p->ent); + } return true; } From a22298731843b6293b7b2ce7a24f68b33e3eddb1 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:33:15 +0000 Subject: [PATCH 051/142] Handle null think fallback --- src/g_phys.cpp | 25 ++++++------ src/g_runthink.h | 33 ++++++++++++++++ src/tests/g_runthink_test.cpp | 71 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 src/g_runthink.h create mode 100644 src/tests/g_runthink_test.cpp diff --git a/src/g_phys.cpp b/src/g_phys.cpp index fe82964..2dabaa7 100644 --- a/src/g_phys.cpp +++ b/src/g_phys.cpp @@ -2,7 +2,7 @@ // Licensed under the GNU General Public License 2.0. // g_phys.c -#include "g_local.h" +#include "g_runthink.h" /* @@ -93,19 +93,16 @@ Runs thinking code for this frame if necessary ============= */ bool G_RunThink(gentity_t *ent) { - gtime_t thinktime = ent->nextthink; - if (thinktime <= 0_ms) - return true; - if (thinktime > level.time) - return true; - - ent->nextthink = 0_ms; - if (!ent->think) - //gi.Com_Error("nullptr ent->think"); - return false; //true; - ent->think(ent); - - return false; + return G_RunThinkImpl( + ent, + level.time, + [](gentity_t *warn_ent) { + const char *name = warn_ent->classname ? warn_ent->classname : ""; + gi.Com_PrintFmt("G_RunThink: null think function for entity \"{}\"\n", name); + }, + [](gentity_t *warn_ent) { + G_FreeEntity(warn_ent); + }); } /* diff --git a/src/g_runthink.h b/src/g_runthink.h new file mode 100644 index 0000000..6f44b8c --- /dev/null +++ b/src/g_runthink.h @@ -0,0 +1,33 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#pragma once + +#include "g_local.h" + +/* +============= +G_RunThinkImpl + +Shared implementation for running entity think functions +============= +*/ +template +bool G_RunThinkImpl(Entity *ent, const gtime_t ¤t_time, Logger &&log_warning, Fallback &&fallback_action) { + gtime_t thinktime = ent->nextthink; + if (thinktime <= 0_ms) + return true; + if (thinktime > current_time) + return true; + + ent->nextthink = 0_ms; + if (!ent->think) { + log_warning(ent); + fallback_action(ent); + return false; + } + + ent->think(ent); + + return false; +} diff --git a/src/tests/g_runthink_test.cpp b/src/tests/g_runthink_test.cpp new file mode 100644 index 0000000..04aba85 --- /dev/null +++ b/src/tests/g_runthink_test.cpp @@ -0,0 +1,71 @@ +#include "g_runthink.h" + +#include +#include +#include + +struct TestEntity { + gtime_t nextthink{}; + void (*think)(TestEntity *) = nullptr; + const char *classname = "test"; + bool freed = false; +}; + +/* +============= +DummyThink + +Marks that a think function ran for testing +============= +*/ +static void DummyThink(TestEntity *ent) { + ent->classname = "ran"; +} + +/* +============= +main + +Entry point for G_RunThinkImpl tests +============= +*/ +int main() { + TestEntity ent{}; + ent.nextthink = 1_ms; + + bool warning_called = false; + std::vector messages; + + auto logger = [&](TestEntity *warn_ent) { + warning_called = true; + messages.emplace_back(warn_ent->classname ? warn_ent->classname : ""); + }; + + auto fallback = [&](TestEntity *warn_ent) { + warn_ent->freed = true; + }; + + bool result = G_RunThinkImpl(&ent, 1_ms, logger, fallback); + + assert(!result); + assert(warning_called); + assert(ent.freed); + assert(ent.nextthink == 0_ms); + assert(messages.size() == 1); + assert(messages.front() == "test"); + + ent.think = DummyThink; + ent.freed = false; + ent.nextthink = 1_ms; + warning_called = false; + messages.clear(); + + result = G_RunThinkImpl(&ent, 1_ms, logger, fallback); + + assert(!result); + assert(!warning_called); + assert(!ent.freed); + assert(ent.classname == std::string("ran")); + + return 0; +} From 27f116759c37bd2d447911d4c332846c7a4ce46a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:33:43 +0000 Subject: [PATCH 052/142] Validate ground entity traces --- docs/regression_tests.md | 12 ++++++++++++ src/g_phys.cpp | 10 ++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 docs/regression_tests.md diff --git a/docs/regression_tests.md b/docs/regression_tests.md new file mode 100644 index 0000000..126f947 --- /dev/null +++ b/docs/regression_tests.md @@ -0,0 +1,12 @@ +# Regression Tests + +## Mover collision resolution +- **Purpose:** Prevent movers from disappearing when collision traces reference freed or invalid entities during ground detection. +- **Setup:** + - Create a simple map with a platform mover intersecting with another entity that can be removed mid-move. + - Instrument the mover so that a collision resolution pass occurs immediately after the other entity is freed. +- **Steps:** + 1. Trigger the mover so it begins translating through the space occupied by the soon-to-be-removed entity. + 2. Remove the obstructing entity during the mover's travel to force a collision trace against an invalid reference. + 3. Allow the mover to complete its motion. +- **Expected result:** The mover finishes its path without being unlinked or lost from the world after the collision trace references an invalid entity. diff --git a/src/g_phys.cpp b/src/g_phys.cpp index fe82964..8e4e76e 100644 --- a/src/g_phys.cpp +++ b/src/g_phys.cpp @@ -134,6 +134,7 @@ The basic solid body movement clip that slides along multiple planes */ void G_FlyMove(gentity_t *ent, float time, contents_t mask) { ent->groundentity = nullptr; + ent->groundentity_linkcount = 0; touch_list_t touch; PM_StepSlideMove_Generic(ent->s.origin, ent->velocity, time, ent->mins, ent->maxs, touch, false, [&](const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { @@ -144,8 +145,13 @@ void G_FlyMove(gentity_t *ent, float time, contents_t mask) { auto &trace = touch.traces[i]; if (trace.plane.normal[2] > 0.7f) { - ent->groundentity = trace.ent; - ent->groundentity_linkcount = trace.ent->linkcount; + if (trace.ent && trace.ent->inuse) { + ent->groundentity = trace.ent; + ent->groundentity_linkcount = trace.ent->linkcount; + } else { + ent->groundentity = nullptr; + ent->groundentity_linkcount = 0; + } } // From 9573bcf81db876708c51129c1159ce08d6c1ce20 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:34:25 +0000 Subject: [PATCH 053/142] Use canonical classname constants in saves --- src/g_local.h | 3 ++ src/g_save.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++++----- src/g_spawn.cpp | 23 +++++++++++++++ 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 211be5f..380f1cd 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -6,6 +6,8 @@ #include "bg_local.h" +#include + // the "gameversion" client command will print this plus compile date constexpr const char *GAMEVERSION = "baseq2"; @@ -2673,6 +2675,7 @@ void G_StuffCmd(gentity_t *e, const char *fmt, ...); // // g_spawn.cpp // +const std::vector &G_GetSpawnClassnameConstants(); void ED_CallSpawn(gentity_t *ent); char *ED_NewString(char *string); void GT_SetLongName(void); diff --git a/src/g_save.cpp b/src/g_save.cpp index 8515559..ec5c95e 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -208,8 +208,9 @@ struct save_type_t { bool never_empty = false; // this should be persisted even if all empty bool (*is_empty)(const void *data) = nullptr; // override default check - void (*read)(void *data, const Json::Value &json, const char *field) = nullptr; // for custom reading - bool (*write)(const void *data, bool null_for_empty, Json::Value &output) = nullptr; // for custom writing +void (*read)(void *data, const Json::Value &json, const char *field) = nullptr; // for custom reading +bool (*write)(const void *data, bool null_for_empty, Json::Value &output) = nullptr; // for custom writing +const char *(*string_resolver)(const char *value) = nullptr; }; struct save_field_t { @@ -225,10 +226,10 @@ struct save_field_t { }; struct save_struct_t { - const char *name; - const std::initializer_list fields; // field list +const char *name; +const std::initializer_list fields; // field list - std::string debug() const { +std::string debug() const { std::stringstream s; for (auto &field : fields) @@ -236,9 +237,54 @@ struct save_struct_t { << field.type.count << '\n'; return s.str(); - } +} }; +static std::unordered_map classname_constants; + +/* +============= +InitClassnameConstants + +Builds a mapping of known classnames to their canonical pointers. +============= +*/ +static void InitClassnameConstants() { + if (!classname_constants.empty()) + return; + + for (const char *classname : G_GetSpawnClassnameConstants()) + classname_constants.emplace(classname, classname); + + for (item_id_t i = static_cast(IT_NULL + 1); i < IT_TOTAL; i = static_cast(i + 1)) { + const gitem_t *item = GetItemByIndex(i); + + if (item && item->classname) + classname_constants.emplace(item->classname, item->classname); + } +} + +/* +============= +Save_ResolveClassname + +Returns the canonical classname pointer for persisted entities. +============= +*/ +static const char *Save_ResolveClassname(const char *classname) { + if (!classname) + return nullptr; + + InitClassnameConstants(); + + auto classname_it = classname_constants.find(classname); + + if (classname_it != classname_constants.end()) + return classname_it->second; + + return nullptr; +} + // field header macro #define SAVE_FIELD(n, f) #f, offsetof(n, f) @@ -560,6 +606,14 @@ struct save_type_deducer> { } \ } +#define FIELD_CLASSNAME(f) \ + { \ + FIELD(f), \ + { \ + ST_STRING, TAG_LEVEL, 0, nullptr, nullptr, false, nullptr, nullptr, nullptr, Save_ResolveClassname \ + } \ + } + // macro for creating save type deducer for // specified struct type #define MAKE_STRUCT_SAVE_DEDUCER(t) \ @@ -919,7 +973,7 @@ FIELD_LEVEL_STRING(model), FIELD_AUTO(freetime), FIELD_LEVEL_STRING(message), -FIELD_LEVEL_STRING(classname), // FIXME: should allow loading from constants +FIELD_CLASSNAME(classname), FIELD_AUTO(spawnflags), FIELD_AUTO(timestamp), @@ -1453,6 +1507,15 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t if (type->count && strlen(json.asCString()) >= type->count) json_print_error(field, "static-length dynamic string overrun", false); else { + if (type->string_resolver) { + const char *resolved = type->string_resolver(json.asCString()); + + if (resolved) { + *((const char **)data) = resolved; + return; + } + } + size_t len = strlen(json.asCString()); size_t alloc_size = type->count ? type->count : (len + 1); char *str = *((char **)data) = (char *)gi.TagMalloc(alloc_size, type->tag); diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 0dc5dd4..653f12a 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -3,6 +3,8 @@ #include "g_local.h" +#include + struct spawn_t { const char *name; void (*spawn)(gentity_t *ent); @@ -457,6 +459,27 @@ static const std::initializer_list spawns = { // clang-format on +/* +============= +G_GetSpawnClassnameConstants + +Provides canonical spawn classname pointers. +============= +*/ +const std::vector &G_GetSpawnClassnameConstants() { + static std::vector classnames; + + if (!classnames.empty()) + return classnames; + + classnames.reserve(spawns.size()); + + for (const spawn_t &spawn : spawns) + classnames.push_back(spawn.name); + + return classnames; +} + static void SpawnEnt_MapFixes(gentity_t *ent) { if (!Q_strcasecmp(level.mapname, "bunk1")) { if (!Q_strcasecmp(ent->classname, "func_button") && !Q_strcasecmp(ent->model, "*36")) { From bb24ffa79128aa1ff32c50a063fe35534ce710fb Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:34:57 +0000 Subject: [PATCH 054/142] Cache hint path chains for monsters --- src/g_ai_new.cpp | 262 ++++++++++++++++------------------------------- 1 file changed, 87 insertions(+), 175 deletions(-) diff --git a/src/g_ai_new.cpp b/src/g_ai_new.cpp index ca565d1..adc1211 100644 --- a/src/g_ai_new.cpp +++ b/src/g_ai_new.cpp @@ -363,20 +363,59 @@ void hintpath_stop(gentity_t *self) { } // ============= -// monsterlost_checkhint - the monster (self) will check around for valid hintpaths. -// a valid hintpath is one where the two endpoints can see both the monster -// and the monster's enemy. if only one person is visible from the endpoints, -// it will not go for it. -// ============= +static bool hint_path_chain_dirty = true; +static int32_t cached_num_hint_paths; +static std::vector cached_hint_path_chain; + +/* +============= +InvalidateHintPathChains + +Marks the cached hint path chains as dirty so they will be rebuilt on next use. +============= +*/ +static void InvalidateHintPathChains() { + hint_path_chain_dirty = true; +} + +/* +============= +BuildCachedHintPathChain + +Populates the flattened hint path chain cache when the cache is dirty or the path layout changed. +============= +*/ +static void BuildCachedHintPathChain() { + if (!hint_path_chain_dirty && cached_num_hint_paths == num_hint_paths) + return; + + cached_hint_path_chain.clear(); + cached_num_hint_paths = num_hint_paths; + + for (int i = 0; i < num_hint_paths; i++) { + for (gentity_t *node = hint_path_start[i]; node; node = node->hint_chain) + cached_hint_path_chain.push_back(node); + } + + hint_path_chain_dirty = false; +} + +/* +============= +monsterlost_checkhint + +The monster (self) will check around for valid hintpaths. A valid hintpath is one where the two endpoints can see both the monster +and the monster's enemy. If only one person is visible from the endpoints, it will not go for it. +============= +*/ bool monsterlost_checkhint(gentity_t *self) { - gentity_t *e, *monster_pathchain, *target_pathchain, *checkpoint = nullptr; - gentity_t *closest; - float closest_range = 1000000; + std::vector monster_pathchain; + std::vector target_pathchain; + float closest_range = 1000000; gentity_t *start, *destination; - int count5 = 0; - float r; - int i; - bool hint_path_represented[MAX_HINT_CHAINS]; + float r; + int i; + bool hint_path_represented[MAX_HINT_CHAINS]; // if there are no hint paths on this map, exit immediately. if (!hint_paths_present) @@ -392,75 +431,17 @@ bool monsterlost_checkhint(gentity_t *self) { if (!strcmp(self->classname, "monster_turret")) return false; - monster_pathchain = nullptr; + BuildCachedHintPathChain(); - // find all the hint_paths. - // FIXME - can we not do this every time? - for (i = 0; i < num_hint_paths; i++) { - e = hint_path_start[i]; - while (e) { - if (e->monster_hint_chain) - e->monster_hint_chain = nullptr; - - if (monster_pathchain) { - checkpoint->monster_hint_chain = e; - checkpoint = e; - } else { - monster_pathchain = e; - checkpoint = e; - } - e = e->hint_chain; - } - } + for (gentity_t *node : cached_hint_path_chain) { + r = realrange(self, node); - // filter them by distance and visibility to the monster - e = monster_pathchain; - checkpoint = nullptr; - while (e) { - r = realrange(self, e); - - if (r > 512) { - if (checkpoint) { - checkpoint->monster_hint_chain = e->monster_hint_chain; - e->monster_hint_chain = nullptr; - e = checkpoint->monster_hint_chain; - continue; - } else { - // use checkpoint as temp pointer - checkpoint = e; - e = e->monster_hint_chain; - checkpoint->monster_hint_chain = nullptr; - // and clear it again - checkpoint = nullptr; - // since we have yet to find a valid one (or else checkpoint would be set) move the - // start of monster_pathchain - monster_pathchain = e; - continue; - } - } - if (!visible(self, e)) { - if (checkpoint) { - checkpoint->monster_hint_chain = e->monster_hint_chain; - e->monster_hint_chain = nullptr; - e = checkpoint->monster_hint_chain; - continue; - } else { - // use checkpoint as temp pointer - checkpoint = e; - e = e->monster_hint_chain; - checkpoint->monster_hint_chain = nullptr; - // and clear it again - checkpoint = nullptr; - // since we have yet to find a valid one (or else checkpoint would be set) move the - // start of monster_pathchain - monster_pathchain = e; - continue; - } - } + if (r > 512) + continue; + if (!visible(self, node)) + continue; - count5++; - checkpoint = e; - e = e->monster_hint_chain; + monster_pathchain.push_back(node); } // at this point, we have a list of all of the eligible hint nodes for the monster @@ -468,95 +449,41 @@ bool monsterlost_checkhint(gentity_t *self) { // seeing whether any can see the player // // first, we figure out which hint chains we have represented in monster_pathchain - if (count5 == 0) + if (monster_pathchain.empty()) return false; for (i = 0; i < num_hint_paths; i++) hint_path_represented[i] = false; - e = monster_pathchain; - checkpoint = nullptr; - while (e) { - if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths)) + for (gentity_t *node : monster_pathchain) { + if ((node->hint_chain_id < 0) || (node->hint_chain_id > num_hint_paths)) return false; - hint_path_represented[e->hint_chain_id] = true; - e = e->monster_hint_chain; + hint_path_represented[node->hint_chain_id] = true; } - count5 = 0; - // now, build the target_pathchain which contains all of the hint_path nodes we need to check for // validity (within range, visibility) - target_pathchain = nullptr; - checkpoint = nullptr; for (i = 0; i < num_hint_paths; i++) { // if this hint chain is represented in the monster_hint_chain, add all of it's nodes to the target_pathchain // for validity checking if (hint_path_represented[i]) { - e = hint_path_start[i]; - while (e) { - if (target_pathchain) { - checkpoint->target_hint_chain = e; - checkpoint = e; - } else { - target_pathchain = e; - checkpoint = e; - } - e = e->hint_chain; - } - } - } + for (gentity_t *node = hint_path_start[i]; node; node = node->hint_chain) { + r = realrange(self->enemy, node); - // target_pathchain is a list of all of the hint_path nodes we need to check for validity relative to the target - e = target_pathchain; - checkpoint = nullptr; - while (e) { - r = realrange(self->enemy, e); - - if (r > 512) { - if (checkpoint) { - checkpoint->target_hint_chain = e->target_hint_chain; - e->target_hint_chain = nullptr; - e = checkpoint->target_hint_chain; - continue; - } else { - // use checkpoint as temp pointer - checkpoint = e; - e = e->target_hint_chain; - checkpoint->target_hint_chain = nullptr; - // and clear it again - checkpoint = nullptr; - target_pathchain = e; - continue; - } - } - if (!visible(self->enemy, e)) { - if (checkpoint) { - checkpoint->target_hint_chain = e->target_hint_chain; - e->target_hint_chain = nullptr; - e = checkpoint->target_hint_chain; - continue; - } else { - // use checkpoint as temp pointer - checkpoint = e; - e = e->target_hint_chain; - checkpoint->target_hint_chain = nullptr; - // and clear it again - checkpoint = nullptr; - target_pathchain = e; - continue; + if (r > 512) + continue; + if (!visible(self->enemy, node)) + continue; + + target_pathchain.push_back(node); } } - - count5++; - checkpoint = e; - e = e->target_hint_chain; } // at this point we should have: - // monster_pathchain - a list of "monster valid" hint_path nodes linked together by monster_hint_chain - // target_pathcain - a list of "target valid" hint_path nodes linked together by target_hint_chain. these + // monster_pathchain - a list of "monster valid" hint_path nodes + // target_pathcain - a list of "target valid" hint_path nodes. these // are filtered such that only nodes which are on the same chain as "monster valid" nodes // // Now, we figure out which "monster valid" node we want to use @@ -568,40 +495,27 @@ bool monsterlost_checkhint(gentity_t *self) { // // Once this filter is finished, we select the closest "monster valid" node, and go to it. - if (count5 == 0) + if (target_pathchain.empty()) return false; // reuse the hint_chain_represented array, this time to see which chains are represented by the target for (i = 0; i < num_hint_paths; i++) hint_path_represented[i] = false; - e = target_pathchain; - checkpoint = nullptr; - while (e) { - if ((e->hint_chain_id < 0) || (e->hint_chain_id > num_hint_paths)) + for (gentity_t *node : target_pathchain) { + if ((node->hint_chain_id < 0) || (node->hint_chain_id > num_hint_paths)) return false; - hint_path_represented[e->hint_chain_id] = true; - e = e->target_hint_chain; + hint_path_represented[node->hint_chain_id] = true; } - // traverse the monster_pathchain - if the hint_node isn't represented in the "target valid" chain list, - // remove it - // if it is on the list, check it for range from the monster. If the range is the closest, keep it - // - closest = nullptr; - e = monster_pathchain; - while (e) { - if (!(hint_path_represented[e->hint_chain_id])) { - checkpoint = e->monster_hint_chain; - e->monster_hint_chain = nullptr; - e = checkpoint; + gentity_t *closest = nullptr; + for (gentity_t *node : monster_pathchain) { + if (!hint_path_represented[node->hint_chain_id]) continue; - } - r = realrange(self, e); + r = realrange(self, node); if (r < closest_range) - closest = e; - e = e->monster_hint_chain; + closest = node; } if (!closest) @@ -614,14 +528,12 @@ bool monsterlost_checkhint(gentity_t *self) { closest = nullptr; closest_range = 10000000; - e = target_pathchain; - while (e) { - if (start->hint_chain_id == e->hint_chain_id) { - r = realrange(self, e); + for (gentity_t *node : target_pathchain) { + if (start->hint_chain_id == node->hint_chain_id) { + r = realrange(self, node); if (r < closest_range) - closest = e; + closest = node; } - e = e->target_hint_chain; } if (!closest) @@ -634,7 +546,6 @@ bool monsterlost_checkhint(gentity_t *self) { return true; } - // // Path code // @@ -726,6 +637,7 @@ void InitHintPaths() { int i; hint_paths_present = 0; + InvalidateHintPathChains(); // check all the hint_paths. e = G_FindByString<&gentity_t::classname>(nullptr, "hint_path"); From d8cced5f016380bd6db7792dfcf0ce3b7d07cc44 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:35:19 +0000 Subject: [PATCH 055/142] Sync chase camera visual effects --- src/g_chase.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/g_chase.cpp b/src/g_chase.cpp index 1390814..0aad04f 100644 --- a/src/g_chase.cpp +++ b/src/g_chase.cpp @@ -2,6 +2,13 @@ // Licensed under the GNU General Public License 2.0. #include "g_local.h" +/* +============= +FreeFollower + +Release a client's current follow target and reset chase state. +============= +*/ void FreeFollower(gentity_t *ent) { if (!ent) return; @@ -22,8 +29,17 @@ void FreeFollower(gentity_t *ent) { ent->client->ps.screen_blend = {}; ent->client->ps.damage_blend = {}; ent->client->ps.rdflags = RDF_NONE; + ent->s.effects = 0; + ent->s.renderfx = 0; } +/* +============= +FreeClientFollowers + +Release all clients following the specified entity. +============= +*/ void FreeClientFollowers(gentity_t *ent) { if (!ent) return; @@ -36,6 +52,13 @@ void FreeClientFollowers(gentity_t *ent) { } } +/* +============= +UpdateChaseCam + +Update the chase camera position and visual state to mirror the target. +============= +*/ void UpdateChaseCam(gentity_t *ent) { vec3_t o, ownerv, goal; gentity_t *targ = ent->client->follow_target; @@ -54,6 +77,13 @@ void UpdateChaseCam(gentity_t *ent) { ownerv = targ->s.origin; oldgoal = ent->s.origin; + // ensure the spectator inherits the target's visual blends and render effects + ent->client->ps.screen_blend = targ->client->ps.screen_blend; + ent->client->ps.damage_blend = targ->client->ps.damage_blend; + ent->client->ps.rdflags = targ->client->ps.rdflags; + ent->s.effects = targ->s.effects; + ent->s.renderfx = targ->s.renderfx; + // Q2Eaks eyecam handling if (g_eyecam->integer) { // mark the chased player as instanced so we can disable their model's visibility From 3ba631a5a264ed8c2409a5108351a957016701fb Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:35:47 +0000 Subject: [PATCH 056/142] Improve water splash handling for piercing shots --- src/g_weapon.cpp | 104 ++++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/src/g_weapon.cpp b/src/g_weapon.cpp index c7e782d..cd5ba73 100644 --- a/src/g_weapon.cpp +++ b/src/g_weapon.cpp @@ -134,57 +134,59 @@ struct fire_lead_pierce_t : pierce_args_t { te_impact(te_impact), mask(mask) {} - // we hit an entity; return false to stop the piercing. - // you can adjust the mask for the re-trace (for water, etc). - bool hit(contents_t &mask, vec3_t &end) override { - // see if we hit water - if (tr.contents & MASK_WATER) { - int color; - - water = true; - water_start = tr.endpos; - - // CHECK: is this compare ever true? - if (te_impact != -1 && start != tr.endpos) { - if (tr.contents & CONTENTS_WATER) { - // FIXME: this effectively does nothing.. - if (strcmp(tr.surface->name, "brwater") == 0) - color = SPLASH_BROWN_WATER; - else - color = SPLASH_BLUE_WATER; - } else if (tr.contents & CONTENTS_SLIME) - color = SPLASH_SLIME; - else if (tr.contents & CONTENTS_LAVA) - color = SPLASH_LAVA; - else - color = SPLASH_UNKNOWN; - - if (color != SPLASH_UNKNOWN) { - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_SPLASH); - gi.WriteByte(8); - gi.WritePosition(tr.endpos); - gi.WriteDir(tr.plane.normal); - gi.WriteByte(color); - gi.multicast(tr.endpos, MULTICAST_PVS, false); - } - - // change bullet's course when it enters water - vec3_t dir, forward, right, up; - dir = end - start; - dir = vectoangles(dir); - AngleVectors(dir, forward, right, up); - float r = crandom() * hspread * 2; - float u = crandom() * vspread * 2; - end = water_start + (forward * 8192); - end += (right * r); - end += (up * u); - } - - // re-trace ignoring water this time - mask &= ~MASK_WATER; - return true; - } + /* + ============= + hit + + Handle trace impacts, including water entry splash and retrace behavior. + ============= + */ + // we hit an entity; return false to stop the piercing. + // you can adjust the mask for the re-trace (for water, etc). + bool hit(contents_t &mask, vec3_t &end) override { + // see if we hit water + if (tr.contents & MASK_WATER) { + int color = SPLASH_UNKNOWN; + + water = true; + water_start = tr.endpos; + + if (tr.contents & CONTENTS_LAVA) + color = SPLASH_LAVA; + else if (tr.contents & CONTENTS_SLIME) + color = SPLASH_SLIME; + else if (tr.contents & CONTENTS_WATER) { + if (tr.surface && strcmp(tr.surface->name, "brwater") == 0) + color = SPLASH_BROWN_WATER; + else + color = SPLASH_BLUE_WATER; + } + + if (te_impact != -1 && color != SPLASH_UNKNOWN) { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_SPLASH); + gi.WriteByte(8); + gi.WritePosition(tr.endpos); + gi.WriteDir(tr.plane.normal); + gi.WriteByte(color); + gi.multicast(tr.endpos, MULTICAST_PVS, false); + } + + // change bullet's course when it enters water + vec3_t dir, forward, right, up; + dir = end - tr.endpos; + dir = vectoangles(dir); + AngleVectors(dir, forward, right, up); + float r = crandom() * hspread * 2; + float u = crandom() * vspread * 2; + end = water_start + (forward * 8192); + end += (right * r); + end += (up * u); + + // re-trace ignoring water this time + mask &= ~MASK_WATER; + return true; + } // did we hit an hurtable entity? if (tr.ent->takedamage) { From b95991ada45695099c87871dd1c096cc3a7c56fc Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:36:18 +0000 Subject: [PATCH 057/142] Handle gravity-aware bottom checks --- src/g_local.h | 4 +- src/g_monster_spawn.cpp | 26 ++++--- src/monsters/m_move.cpp | 158 ++++++++++++++++++++++++---------------- 3 files changed, 114 insertions(+), 74 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 211be5f..fbb3bff 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -3098,8 +3098,8 @@ extern byte damage_multiplier; // // m_move.cpp // -bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, bool ceiling); -bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &absmins, const vec3_t &absmaxs, gentity_t *ignore, contents_t mask, bool ceiling, bool allow_any_step_height); +bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, const vec3_t &gravityVector); +bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &absmins, const vec3_t &absmaxs, gentity_t *ignore, contents_t mask, const vec3_t &gravityVector, bool allow_any_step_height); bool M_CheckBottom(gentity_t *ent); bool G_CloseEnough(gentity_t *ent, gentity_t *goal, float dist); bool M_walkmove(gentity_t *ent, float yaw, float dist); diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index d47e7f8..1d85a01 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -122,25 +122,29 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max return true; } -// -// CheckGroundSpawnPoint -// -// PMM - used for walking monsters -// checks: -// 1) is there a ground within the specified height of the origin? -// 2) is the ground non-water? -// 3) is the ground flat enough to walk on? -// +/* +============= +CheckGroundSpawnPoint + +PMM - used for walking monsters +checks: + 1) is there a ground within the specified height of the origin? + 2) is the ground non-water? + 3) is the ground flat enough to walk on? +============= +*/ bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, float gravity) { if (!CheckSpawnPoint(origin, entMins, entMaxs)) return false; - if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, false)) + vec3_t gravityVector{ 0.0f, 0.0f, gravity }; + + if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, gravityVector)) return true; - if (M_CheckBottom_Slow_Generic(origin, entMins, entMaxs, nullptr, MASK_MONSTERSOLID, false, false)) + if (M_CheckBottom_Slow_Generic(origin, entMins, entMaxs, nullptr, MASK_MONSTERSOLID, gravityVector, false)) return true; return false; diff --git a/src/monsters/m_move.cpp b/src/monsters/m_move.cpp index 9af6db5..b2f4208 100644 --- a/src/monsters/m_move.cpp +++ b/src/monsters/m_move.cpp @@ -10,25 +10,43 @@ gentity_t *new_bad; // pmm /* ============= -M_CheckBottom - -Returns false if any part of the bottom of the entity is off an edge that -is not a staircase. +M_CheckBottom_Fast_Generic +Quickly checks whether the entity bounds have support along the provided gravity direction. ============= */ -bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, bool ceiling) { - // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors - vec3_t start; - - start[2] = absmins[2] - 1; - if (ceiling) - start[2] = absmaxs[2] + 1; - - for (int x = 0; x <= 1; x++) - for (int y = 0; y <= 1; y++) { - start[0] = x ? absmaxs[0] : absmins[0]; - start[1] = y ? absmaxs[1] : absmins[1]; +bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, const vec3_t &gravityVector) { + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + int gravity_axis = 0; + float gravity_axis_abs = fabsf(gravity_dir[0]); + + for (int axis = 1; axis < 3; axis++) { + float abs_val = fabsf(gravity_dir[axis]); + + if (abs_val > gravity_axis_abs) { + gravity_axis = axis; + gravity_axis_abs = abs_val; + } + } + + int axis_a = (gravity_axis + 1) % 3; + int axis_b = (gravity_axis + 2) % 3; + float gravity_sign = gravity_dir[gravity_axis] >= 0.0f ? 1.0f : -1.0f; + + vec3_t start = {}; + + start[axis_a] = absmins[axis_a]; + start[axis_b] = absmins[axis_b]; + start[gravity_axis] = (gravity_sign < 0.0f ? absmins[gravity_axis] : absmaxs[gravity_axis]) + gravity_sign; + + for (int axis_a_val = 0; axis_a_val <= 1; axis_a_val++) + for (int axis_b_val = 0; axis_b_val <= 1; axis_b_val++) { + start[axis_a] = axis_a_val ? absmaxs[axis_a] : absmins[axis_a]; + start[axis_b] = axis_b_val ? absmaxs[axis_b] : absmins[axis_b]; if (gi.pointcontents(start) != CONTENTS_SOLID) return false; } @@ -36,36 +54,53 @@ bool M_CheckBottom_Fast_Generic(const vec3_t &absmins, const vec3_t &absmaxs, bo return true; // we got out easy } -bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, gentity_t *ignore, contents_t mask, bool ceiling, bool allow_any_step_height) { - vec3_t start; +/* +============= +M_CheckBottom_Slow_Generic - // - // check it for real... - // - vec3_t step_quadrant_size = (maxs - mins) * 0.5f; - step_quadrant_size.z = 0; +Full bottom support check that allows for step heights along the gravity axis. +============= +*/ +bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, gentity_t *ignore, contents_t mask, const vec3_t &gravityVector, bool allow_any_step_height) { +vec3_t gravity_dir = gravityVector.normalized(); - vec3_t half_step_quadrant = step_quadrant_size * 0.5f; - vec3_t half_step_quadrant_mins = -half_step_quadrant; + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; - vec3_t stop; + int gravity_axis = 0; + float gravity_axis_abs = fabsf(gravity_dir[0]); - start[0] = stop[0] = origin.x; - start[1] = stop[1] = origin.y; + for (int axis = 1; axis < 3; axis++) { + float abs_val = fabsf(gravity_dir[axis]); - if (!ceiling) { - start[2] = origin.z + mins.z; - stop[2] = start[2] - STEPSIZE * 2; - } else { - start[2] = origin.z + maxs.z; - stop[2] = start[2] + STEPSIZE * 2; + if (abs_val > gravity_axis_abs) { + gravity_axis = axis; + gravity_axis_abs = abs_val; + } } - vec3_t mins_no_z = mins; - vec3_t maxs_no_z = maxs; - mins_no_z.z = maxs_no_z.z = 0; + int axis_a = (gravity_axis + 1) % 3; + int axis_b = (gravity_axis + 2) % 3; + float gravity_sign = gravity_dir[gravity_axis] >= 0.0f ? 1.0f : -1.0f; - trace_t trace = gi.trace(start, mins_no_z, maxs_no_z, stop, ignore, mask); + vec3_t start = origin; + vec3_t stop = origin; + + start[gravity_axis] += gravity_sign < 0.0f ? mins[gravity_axis] : maxs[gravity_axis]; + stop[gravity_axis] = start[gravity_axis] + (gravity_sign * STEPSIZE * 2); + + vec3_t step_quadrant_size = (maxs - mins) * 0.5f; + step_quadrant_size[gravity_axis] = 0.0f; + + vec3_t half_step_quadrant = step_quadrant_size * 0.5f; + vec3_t half_step_quadrant_mins = -half_step_quadrant; + + vec3_t mins_no_gravity = mins; + vec3_t maxs_no_gravity = maxs; + mins_no_gravity[gravity_axis] = 0.0f; + maxs_no_gravity[gravity_axis] = 0.0f; + + trace_t trace = gi.trace(start, mins_no_gravity, maxs_no_gravity, stop, ignore, mask); if (trace.fraction == 1.0f) return false; @@ -74,53 +109,54 @@ bool M_CheckBottom_Slow_Generic(const vec3_t &origin, const vec3_t &mins, const if (allow_any_step_height) return true; - start[0] = stop[0] = origin.x + ((mins.x + maxs.x) * 0.5f); - start[1] = stop[1] = origin.y + ((mins.y + maxs.y) * 0.5f); + start[axis_a] = stop[axis_a] = origin[axis_a] + ((mins[axis_a] + maxs[axis_a]) * 0.5f); + start[axis_b] = stop[axis_b] = origin[axis_b] + ((mins[axis_b] + maxs[axis_b]) * 0.5f); - float mid = trace.endpos[2]; + float mid = trace.endpos[gravity_axis]; // the corners must be within 16 of the midpoint - for (int32_t x = 0; x <= 1; x++) - for (int32_t y = 0; y <= 1; y++) { + for (int32_t axis_a_val = 0; axis_a_val <= 1; axis_a_val++) + for (int32_t axis_b_val = 0; axis_b_val <= 1; axis_b_val++) { vec3_t quadrant_start = start; - if (x) - quadrant_start.x += half_step_quadrant.x; + if (axis_a_val) + quadrant_start[axis_a] += half_step_quadrant[axis_a]; else - quadrant_start.x -= half_step_quadrant.x; + quadrant_start[axis_a] -= half_step_quadrant[axis_a]; - if (y) - quadrant_start.y += half_step_quadrant.y; + if (axis_b_val) + quadrant_start[axis_b] += half_step_quadrant[axis_b]; else - quadrant_start.y -= half_step_quadrant.y; + quadrant_start[axis_b] -= half_step_quadrant[axis_b]; vec3_t quadrant_end = quadrant_start; - quadrant_end.z = stop.z; + quadrant_end[gravity_axis] = stop[gravity_axis]; trace = gi.trace(quadrant_start, half_step_quadrant_mins, half_step_quadrant, quadrant_end, ignore, mask); - // FIXME - this will only handle 0,0,1 and 0,0,-1 gravity vectors - if (ceiling) { - if (trace.fraction == 1.0f || trace.endpos[2] - mid > (STEPSIZE)) - return false; - } else { - if (trace.fraction == 1.0f || mid - trace.endpos[2] > (STEPSIZE)) - return false; - } - } + if (trace.fraction == 1.0f || ((trace.endpos[gravity_axis] - mid) * gravity_sign) > STEPSIZE) + return false; +} return true; } +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that is not a staircase. +============= +*/ bool M_CheckBottom(gentity_t *ent) { // if all of the points under the corners are solid world, don't bother // with the tougher checks - if (M_CheckBottom_Fast_Generic(ent->s.origin + ent->mins, ent->s.origin + ent->maxs, ent->gravityVector[2] > 0)) + if (M_CheckBottom_Fast_Generic(ent->s.origin + ent->mins, ent->s.origin + ent->maxs, ent->gravityVector)) return true; // we got out easy contents_t mask = (ent->svflags & SVF_MONSTER) ? MASK_MONSTERSOLID : (MASK_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER); - return M_CheckBottom_Slow_Generic(ent->s.origin, ent->mins, ent->maxs, ent, mask, ent->gravityVector[2] > 0, ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP)); + return M_CheckBottom_Slow_Generic(ent->s.origin, ent->mins, ent->maxs, ent, mask, ent->gravityVector, ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP)); } static bool IsBadAhead(gentity_t *self, gentity_t *bad, const vec3_t &move) { From 1aeaa9fad8810d9979f4bf5836302c287d887654 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:36:43 +0000 Subject: [PATCH 058/142] Adjust grenade explosion origin --- src/g_weapon.cpp | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/g_weapon.cpp b/src/g_weapon.cpp index c7e782d..5021e60 100644 --- a/src/g_weapon.cpp +++ b/src/g_weapon.cpp @@ -558,22 +558,33 @@ constexpr spawnflags_t SPAWNFLAG_GRENADE_HAND = 1_spawnflag; constexpr spawnflags_t SPAWNFLAG_GRENADE_HELD = 2_spawnflag; /* -================= -fire_grenade -================= +============= +Grenade_Explode + +Handle grenade detonation damage and visual effects. +============= */ static THINK(Grenade_Explode) (gentity_t *ent) -> void { - vec3_t origin; - mod_t mod; + vec3_t explosion_origin; + vec3_t origin; + mod_t mod; + + explosion_origin = ent->s.origin; + if (ent->groundentity) { + explosion_origin[2] += 4.f; + vec3_t delta = explosion_origin - ent->s.origin; + ent->s.origin = explosion_origin; + ent->absmin += delta; + ent->absmax += delta; + } if (ent->owner->client) PlayerNoise(ent->owner, ent->s.origin, PNOISE_IMPACT); - // FIXME: if we are onground then raise our Z just a bit since we are a point? if (ent->enemy) { - float points; - vec3_t v; - vec3_t dir; + float points; + vec3_t v; + vec3_t dir; v = ent->enemy->mins + ent->enemy->maxs; v = ent->enemy->s.origin + (v * 0.5f); @@ -617,6 +628,7 @@ static THINK(Grenade_Explode) (gentity_t *ent) -> void { G_FreeEntity(ent); } + static TOUCH(Grenade_Touch) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool other_touching_self) -> void { if (other == ent->owner) return; From 7476245858f1b0ac512c0838965b998dadcb649e Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:37:15 +0000 Subject: [PATCH 059/142] Align stationary trigger spawn cleanup --- src/g_monster.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/g_monster.cpp b/src/g_monster.cpp index 882eab7..427b32f 100644 --- a/src/g_monster.cpp +++ b/src/g_monster.cpp @@ -948,12 +948,41 @@ USE(monster_use) (gentity_t *self, gentity_t *other, gentity_t *activator) -> vo void monster_start_go(gentity_t *self); +/* +============= +monster_clear_trigger_spawn_state + +Clears trigger-spawn state so the monster behaves like a standard spawn. +============= +*/ +static void monster_clear_trigger_spawn_state(gentity_t *self) { + self->svflags &= ~SVF_NOCLIENT; + + if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN)) + self->spawnflags &= ~SPAWNFLAG_MONSTER_TRIGGER_SPAWN; + + if (self->monsterinfo.aiflags & AI_DO_NOT_COUNT) { + self->monsterinfo.aiflags &= ~AI_DO_NOT_COUNT; + + if (!self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD)) { + if (g_debug_monster_kills->integer) + level.monsters_registered[level.total_monsters] = self; + level.total_monsters++; + } + } +} + +/* +============= +monster_triggered_spawn +============= +*/ static THINK(monster_triggered_spawn) (gentity_t *self) -> void { self->s.origin[2] += 1; self->solid = SOLID_BBOX; self->movetype = MOVETYPE_STEP; - self->svflags &= ~SVF_NOCLIENT; + monster_clear_trigger_spawn_state(self); self->air_finished = level.time + 12_sec; gi.linkentity(self); @@ -1620,18 +1649,22 @@ void monster_fire_heatbeam(gentity_t *self, const vec3_t &start, const vec3_t &d void stationarymonster_start_go(gentity_t *self); +/* +============= +stationarymonster_triggered_spawn + +Activates a trigger-spawned stationary monster and converts it to standard behavior. +============= +*/ static THINK(stationarymonster_triggered_spawn) (gentity_t *self) -> void { self->solid = SOLID_BBOX; self->movetype = MOVETYPE_NONE; - self->svflags &= ~SVF_NOCLIENT; + monster_clear_trigger_spawn_state(self); self->air_finished = level.time + 12_sec; gi.linkentity(self); KillBox(self, false); - // FIXME - why doesn't this happen with real monsters? - self->spawnflags &= ~SPAWNFLAG_MONSTER_TRIGGER_SPAWN; - stationarymonster_start_go(self); if (self->enemy && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && !(self->enemy->flags & FL_NOTARGET)) { @@ -1644,6 +1677,13 @@ static THINK(stationarymonster_triggered_spawn) (gentity_t *self) -> void { } } +/* +============= +stationarymonster_triggered_spawn_use + +Entry point when a trigger fires a stationary monster. +============= +*/ static USE(stationarymonster_triggered_spawn_use) (gentity_t *self, gentity_t *other, gentity_t *activator) -> void { // we have a one frame delay here so we don't telefrag the guy who activated us self->think = stationarymonster_triggered_spawn; From 191840ceca3628477ab9e09ca4a7d26fe53e5a37 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:37:53 +0000 Subject: [PATCH 060/142] Add level steam effect id tracking --- src/g_local.h | 1 + src/g_save.cpp | 1 + src/g_spawn.cpp | 3 ++- src/g_target.cpp | 49 ++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 211be5f..0b19538 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1561,6 +1561,7 @@ struct level_locals_t { int32_t body_que; // dead bodies int32_t power_cubes; // ugly necessity for coop + int32_t steam_effect_next_id; gentity_t *disguise_violator; gtime_t disguise_violation_time; diff --git a/src/g_save.cpp b/src/g_save.cpp index 8515559..be1bc2c 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -644,6 +644,7 @@ FIELD_AUTO(killed_monsters), FIELD_AUTO(body_que), FIELD_AUTO(power_cubes), +FIELD_AUTO(steam_effect_next_id), FIELD_AUTO(disguise_violator), FIELD_AUTO(disguise_violation_time), diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index 0dc5dd4..466ed32 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1810,8 +1810,9 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp gi.FreeTags(TAG_LEVEL); memset(&level, 0, sizeof(level)); + level.steam_effect_next_id = 0; memset(g_entities, 0, game.maxentities * sizeof(g_entities[0])); -globals.num_entities = game.maxclients + 1; + globals.num_entities = game.maxclients + 1; level.entstring = incoming_entstring; entities = level.entstring.c_str(); diff --git a/src/g_target.cpp b/src/g_target.cpp index c887df9..771415b 100644 --- a/src/g_target.cpp +++ b/src/g_target.cpp @@ -1979,15 +1979,34 @@ good colors to use: 232 - blood */ -static USE(use_target_steam) (gentity_t *self, gentity_t *other, gentity_t *activator) -> void { - // FIXME - this needs to be a global - static int nextid; - vec3_t point; - if (nextid > 20000) - nextid = nextid % 20000; +/* +============= +GetNextSteamEffectID + +Returns the next steam effect identifier for this level, wrapping to avoid +overflowing the protocol limit. +============= +*/ +static int GetNextSteamEffectID() { + if (level.steam_effect_next_id > 20000) + level.steam_effect_next_id %= 20000; - nextid++; + level.steam_effect_next_id++; + + return level.steam_effect_next_id; +} + +/* +============= +use_target_steam + +Activates the steam effect and sends the configured temp entity to clients. +============= +*/ +static USE(use_target_steam) (gentity_t *self, gentity_t *other, gentity_t *activator) -> void { + const int steam_id = GetNextSteamEffectID(); + vec3_t point; // automagically set wait from func_timer unless they set it already, or // default to 1000 if not called by a func_timer (eek!) @@ -2008,7 +2027,7 @@ static USE(use_target_steam) (gentity_t *self, gentity_t *other, gentity_t *acti if (self->wait > 100) { gi.WriteByte(svc_temp_entity); gi.WriteByte(TE_STEAM); - gi.WriteShort(nextid); + gi.WriteShort(steam_id); gi.WriteByte(self->count); gi.WritePosition(self->s.origin); gi.WriteDir(self->movedir); @@ -2029,6 +2048,13 @@ static USE(use_target_steam) (gentity_t *self, gentity_t *other, gentity_t *acti } } +/* +============= +target_steam_start + +Initializes steam parameters and prepares the entity for activation. +============= +*/ static THINK(target_steam_start) (gentity_t *self) -> void { gentity_t *ent; @@ -2061,6 +2087,13 @@ static THINK(target_steam_start) (gentity_t *self) -> void { gi.linkentity(self); } +/* +============= +SP_target_steam + +Spawns a steam target and defers initialization if linked to a target. +============= +*/ void SP_target_steam(gentity_t *self) { self->style = (int)self->speed; From 781bcd299f06a3fdab3524339737d6bce5a88d07 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:39:58 +0000 Subject: [PATCH 061/142] Guard BroadcastFriendlyMessage when message is missing --- src/g_utils.cpp | 4 ++++ src/g_utils_friendly_message.h | 13 +++++++++++++ tests/friendly_message_tests.cpp | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/g_utils_friendly_message.h create mode 100644 tests/friendly_message_tests.cpp diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 5fe2084..42c2355 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -4,6 +4,7 @@ #include "g_activation.h" #include "g_local.h" +#include "g_utils_friendly_message.h" #include #include #include "g_utils_target_selection.h" @@ -153,6 +154,9 @@ active players. ============= */ void BroadcastFriendlyMessage(team_t team, const char *msg) { + if (!FriendlyMessageHasText(msg)) + return; + for (auto ce : active_clients()) { const bool playing = ClientIsPlaying(ce->client); if (!playing) { diff --git a/src/g_utils_friendly_message.h b/src/g_utils_friendly_message.h new file mode 100644 index 0000000..e169695 --- /dev/null +++ b/src/g_utils_friendly_message.h @@ -0,0 +1,13 @@ +/* +============= +FriendlyMessageHasText + +Determines if the provided message pointer is non-null and non-empty. +============= +*/ +#pragma once + +inline bool FriendlyMessageHasText(const char *msg) +{ + return msg && *msg; +} diff --git a/tests/friendly_message_tests.cpp b/tests/friendly_message_tests.cpp new file mode 100644 index 0000000..b06d274 --- /dev/null +++ b/tests/friendly_message_tests.cpp @@ -0,0 +1,18 @@ +#include "../src/g_utils_friendly_message.h" +#include + +/* +============= +main + +Regression coverage for friendly message validation. +============= +*/ +int main() +{ + assert(!FriendlyMessageHasText(nullptr)); + assert(!FriendlyMessageHasText("")); + assert(FriendlyMessageHasText("hello")); + + return 0; +} From ec38fb263efe557ce8f222ee7ed450fc3c31b62f Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 20:46:40 +0000 Subject: [PATCH 062/142] Add configurable doppelganger pickup limit --- src/g_items.cpp | 7 ++++--- src/g_local.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/g_items.cpp b/src/g_items.cpp index 5d06245..c9e9871 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -1857,10 +1857,10 @@ static bool Pickup_Doppelganger(gentity_t *ent, gentity_t *other) { if (!deathmatch->integer) return false; - max_allowed = G_GetHoldableMax(g_dm_holdable_doppel_max->integer, ent->item->quantity_warn, 1); + max_allowed = G_GetHoldableMax(g_dm_holdable_doppel_max->integer, ent->item->quantity_max, 1); quantity = other->client->pers.inventory[ent->item->id]; if (quantity >= max_allowed) { - gi.cprintf(other, PRINT_LOW, "You can't carry more %s\n", ent->item->pickup_name); + gi.cprintf(other, PRINT_LOW, "You can only carry %d %s\n", max_allowed, ent->item->pickup_name); return false; } @@ -5078,7 +5078,8 @@ model="models/items/dopple/tris.md2" /* tag */ POWERUP_DOPPELGANGER, /* precaches */ "models/objects/dopplebase/tris.md2 models/items/spawngro3/tris.md2 medic_commander/monsterspawn1.wav models/items/hunter/tris.md2 models/items/vengnce/tris.md2", /* sort_id */ 0, -/* quantity_warn */ 1 +/* quantity_warn */ 1, +/* quantity_max */ 1 }, /* Tag Token */ diff --git a/src/g_local.h b/src/g_local.h index 7472574..15bc5a4 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1303,6 +1303,7 @@ struct gitem_t { int32_t sort_id = 0; // used by some items to control their sorting int32_t quantity_warn = 5; // when to warn on low ammo + int32_t quantity_max = 0; // maximum quantity a holdable can stack to (0 = use fallback) // set in InitItems, don't set by hand // circular list of chained weapons From 95b4f1563fc5a72426cde60a6c9b03c4fb373a13 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Wed, 19 Nov 2025 21:21:03 +0000 Subject: [PATCH 063/142] Handle spawn checks with gravity vectors --- src/g_items.cpp | 4 +- src/g_local.h | 12 +-- src/g_monster.cpp | 38 ++++--- src/g_monster_spawn.cpp | 99 ++++++++++++----- src/monsters/m_carrier.cpp | 13 ++- src/monsters/m_medic.cpp | 16 +-- src/monsters/m_widow.cpp | 4 +- src/monsters/m_widow2.cpp | 4 +- tests/spawn_gravity_tests.cpp | 198 ++++++++++++++++++++++++++++++++++ 9 files changed, 321 insertions(+), 67 deletions(-) create mode 100644 tests/spawn_gravity_tests.cpp diff --git a/src/g_items.cpp b/src/g_items.cpp index 5d06245..b6a17de 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -1830,10 +1830,10 @@ static void Use_Doppelganger(gentity_t *ent, gitem_t *item) { createPt = ent->s.origin + (forward * 48); - if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32)) + if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32, true, ent->gravityVector)) return; - if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1)) + if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, ent->gravityVector)) return; ent->client->pers.inventory[item->id]--; diff --git a/src/g_local.h b/src/g_local.h index 7472574..182f5c2 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -2789,7 +2789,7 @@ void monster_fire_bfg(gentity_t *self, const vec3_t &start, const vec3_t &aimdir bool M_CheckClearShot(gentity_t *self, const vec3_t &offset); bool M_CheckClearShot(gentity_t *self, const vec3_t &offset, vec3_t &start); vec3_t M_ProjectFlashSource(gentity_t *self, const vec3_t &offset, const vec3_t &forward, const vec3_t &right); -bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, bool ceiling, gentity_t *ignore, contents_t mask, bool allow_partial); +bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector, gentity_t *ignore, contents_t mask, bool allow_partial); bool M_droptofloor(gentity_t *ent); void monster_think(gentity_t *self); void monster_dead_think(gentity_t *self); @@ -3228,11 +3228,11 @@ gentity_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const ve const char *classname); gentity_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname, float height); -bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, - float maxMoveUp, bool drop = true); -bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs); -bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, - float gravity); +bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, + float maxMoveUp, bool drop = true, const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); +bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); +bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, + const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size); void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles); diff --git a/src/g_monster.cpp b/src/g_monster.cpp index 427b32f..75a82e2 100644 --- a/src/g_monster.cpp +++ b/src/g_monster.cpp @@ -307,24 +307,26 @@ void M_WorldEffects(gentity_t *ent) { } } -bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, bool ceiling, gentity_t *ignore, contents_t mask, bool allow_partial) { - vec3_t end; - trace_t trace; +/* +============= +M_droptofloor_generic - if (gi.trace(origin, mins, maxs, origin, ignore, mask).startsolid) { - if (!ceiling) - origin[2] += 1; - else - origin[2] -= 1; - } +Drops an origin along the provided gravity vector until contact is made or a blocking +volume is found. +============= +*/ +bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector, gentity_t *ignore, contents_t mask, bool allow_partial) { + vec3_t gravity_dir = gravityVector.normalized(); - if (!ceiling) { - end = origin; - end[2] -= 256; - } else { - end = origin; - end[2] += 256; - } + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + trace_t trace = gi.trace(origin, mins, maxs, origin, ignore, mask); + + if (trace.startsolid) + origin -= gravity_dir; + + vec3_t end = origin + (gravity_dir * 256.0f); trace = gi.trace(origin, mins, maxs, end, ignore, mask); @@ -336,11 +338,12 @@ bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &max return true; } + bool M_droptofloor(gentity_t *ent) { contents_t mask = G_GetClipMask(ent); if (!ent->spawnflags.has(SPAWNFLAG_MONSTER_NO_DROP)) { - if (!M_droptofloor_generic(ent->s.origin, ent->mins, ent->maxs, ent->gravityVector[2] > 0, ent, mask, true)) + if (!M_droptofloor_generic(ent->s.origin, ent->mins, ent->maxs, ent->gravityVector, ent, mask, true)) return false; } else { if (gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, ent, mask).startsolid) @@ -354,6 +357,7 @@ bool M_droptofloor(gentity_t *ent) { return true; } + void M_SetEffects(gentity_t *ent) { ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN | EF_DOUBLE | EF_QUAD | EF_PENT | EF_FLIES); ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE | RF_SHELL_DOUBLE); diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index 1d85a01..bff4c2a 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -21,35 +21,54 @@ // FIXME - for the black widow, if we want the stalkers coming in on the roof, we'll have to tweak some things -// -// CreateMonster -// +/* +============= +CreateMonster + +Spawns a monster entity with default downward gravity. +============= +*/ +#ifndef MONSTER_SPAWN_TESTS gentity_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname) { gentity_t *newEnt; - + newEnt = G_Spawn(); - + newEnt->s.origin = origin; newEnt->s.angles = angles; newEnt->classname = classname; newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; - + newEnt->gravityVector = { 0, 0, -1 }; ED_CallSpawn(newEnt); newEnt->s.renderfx |= RF_IR_VISIBLE; - + return newEnt; } +/* +============= +CreateFlyMonster + +Validates a spawn point for a flying monster and creates it if clear. +============= +*/ gentity_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname) { - if (!CheckSpawnPoint(origin, mins, maxs)) + if (!CheckSpawnPoint(origin, mins, maxs, { 0.0f, 0.0f, -1.0f })) return nullptr; return (CreateMonster(origin, angles, classname)); } +/* +============= +CreateGroundMonster + +Checks ground viability before creating a walking monster. +============= +*/ // This is just a wrapper for CreateMonster that looks down height # of CMUs and sees if there // are bad things down there or not @@ -58,7 +77,7 @@ gentity_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const gentity_t *newEnt; // check the ground to make sure it's there, it's relatively flat, and it's not toxic - if (!CheckGroundSpawnPoint(origin, entMins, entMaxs, height, -1.f)) + if (!CheckGroundSpawnPoint(origin, entMins, entMaxs, height, { 0.0f, 0.0f, -1.0f })) return nullptr; newEnt = CreateMonster(origin, angles, classname); @@ -68,16 +87,29 @@ gentity_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const return newEnt; } -// FindSpawnPoint -// PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point -// if the startpoint is bad, try above the startpoint for a bit +#endif // MONSTER_SPAWN_TESTS -bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop) + + +/* +============= +FindSpawnPoint + +PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point. +If the start point is bad, try above the start point for a bit. +============= +*/ +bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop, const vec3_t &gravityVector) { spawnpoint = startpoint; + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + // drop first - if (!drop || !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false)) + if (!drop || !M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false)) { spawnpoint = startpoint; @@ -88,30 +120,37 @@ bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t & return false; // fixed, so drop again - if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false)) + if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false)) return false; // ??? } return true; } + // FIXME - all of this needs to be tweaked to handle the new gravity rules // if we ever want to spawn stuff on the roof -// -// CheckSpawnPoint -// -// PMM - checks volume to make sure we can spawn a monster there (is it solid?) -// -// This is all fliers should need +/* +============= +CheckSpawnPoint -bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs) +PMM - checks volume to make sure we can spawn a monster there (is it solid?) +This is all fliers should need. +============= +*/ +bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector) { trace_t tr; if (!mins || !maxs) return false; + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; @@ -119,9 +158,16 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max if (tr.ent != world) return false; + vec3_t projected_origin = origin + gravity_dir; + + tr = gi.trace(origin, mins, maxs, projected_origin, nullptr, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + return false; + return true; } + /* ============= CheckGroundSpawnPoint @@ -134,13 +180,11 @@ PMM - used for walking monsters ============= */ -bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, float gravity) +bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, const vec3_t &gravityVector) { - if (!CheckSpawnPoint(origin, entMins, entMaxs)) + if (!CheckSpawnPoint(origin, entMins, entMaxs, gravityVector)) return false; - vec3_t gravityVector{ 0.0f, 0.0f, gravity }; - if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, gravityVector)) return true; @@ -150,6 +194,8 @@ bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const ve return false; } + +#ifndef MONSTER_SPAWN_TESTS // **************************** // SPAWNGROW stuff // **************************** @@ -365,3 +411,4 @@ void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles) ent->nextthink = level.time + 10_hz; gi.linkentity(ent); } +#endif // MONSTER_SPAWN_TESTS diff --git a/src/monsters/m_carrier.cpp b/src/monsters/m_carrier.cpp index a4f16ad..cb9b3b2 100644 --- a/src/monsters/m_carrier.cpp +++ b/src/monsters/m_carrier.cpp @@ -58,7 +58,12 @@ void carrier_reattack_gren(gentity_t *self); void carrier_start_spawn(gentity_t *self); void carrier_spawn_check(gentity_t *self); -void carrier_prep_spawn(gentity_t *self); +void carrier_prep_spawn(gentity_t *self) { + CarrierCoopCheck(self); + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->timestamp = level.time; + self->yaw_speed = 10; +} void CarrierMachineGunHold(gentity_t *self); void CarrierRocket(gentity_t *self); @@ -311,7 +316,7 @@ static void CarrierSpawn(gentity_t *self) { auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[0]]; - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false)) { + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false, self->gravityVector)) { ent = CreateFlyMonster(spawnpoint, self->s.angles, reinforcement.mins, reinforcement.maxs, reinforcement.classname); if (!ent) @@ -357,7 +362,7 @@ void carrier_prep_spawn(gentity_t *self) { self->monsterinfo.aiflags |= AI_MANUAL_STEERING; self->timestamp = level.time; self->yaw_speed = 10; -} + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false, self->gravityVector)) { void carrier_spawn_check(gentity_t *self) { CarrierCoopCheck(self); @@ -398,7 +403,7 @@ static void carrier_ready_spawn(gentity_t *self) { offset = { 105, 0, -58 }; AngleVectors(self->s.angles, f, r, nullptr); startpoint = M_ProjectFlashSource(self, offset, f, r); - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false)) { + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false, self->gravityVector)) { float radius = (reinforcement.maxs - reinforcement.mins).length() * 0.5f; SpawnGrow_Spawn(spawnpoint + (reinforcement.mins + reinforcement.maxs), radius, radius * 2.f); diff --git a/src/monsters/m_medic.cpp b/src/monsters/m_medic.cpp index 3cdd935..5dc0827 100644 --- a/src/monsters/m_medic.cpp +++ b/src/monsters/m_medic.cpp @@ -1042,8 +1042,8 @@ static void medic_determine_spawn(gentity_t *self) { auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) { - if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, -1)) { + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, true, self->gravityVector)) { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, self->gravityVector)) { num_success++; // we found a spot, we're done here count = num_summoned; @@ -1068,8 +1068,8 @@ static void medic_determine_spawn(gentity_t *self) { auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) { - if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, -1)) { + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, true, self->gravityVector)) { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, self->gravityVector)) { num_success++; // we found a spot, we're done here count = num_summoned; @@ -1127,8 +1127,8 @@ static void medic_spawngrows(gentity_t *self) { auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) { - if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, -1)) { + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, true, self->gravityVector)) { + if (CheckGroundSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, 256, self->gravityVector)) { num_success++; float radius = (reinforcement.maxs - reinforcement.mins).length() * 0.5f; SpawnGrow_Spawn(spawnpoint + (reinforcement.mins + reinforcement.maxs), radius, radius * 2.f); @@ -1165,8 +1165,8 @@ static void medic_finish_spawn(gentity_t *self) { startpoint[2] += 10 * (self->s.scale ? self->s.scale : 1.0f); ent = nullptr; - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32)) { - if (CheckSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs)) + if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, true, self->gravityVector)) { + if (CheckSpawnPoint(spawnpoint, reinforcement.mins, reinforcement.maxs, self->gravityVector)) ent = CreateGroundMonster(spawnpoint, self->s.angles, reinforcement.mins, reinforcement.maxs, reinforcement.classname, 256); } diff --git a/src/monsters/m_widow.cpp b/src/monsters/m_widow.cpp index e8589cd..e750a4e 100644 --- a/src/monsters/m_widow.cpp +++ b/src/monsters/m_widow.cpp @@ -246,7 +246,7 @@ static void WidowSpawn(gentity_t *self) { startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); - if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) { + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64, true, self->gravityVector)) { ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256); if (!ent) @@ -299,7 +299,7 @@ static void widow_ready_spawn(gentity_t *self) { for (i = 0; i < 2; i++) { offset = spawnpoints[i]; startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); - if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) { + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64, true, self->gravityVector)) { float radius = (stalker_maxs - stalker_mins).length() * 0.5f; SpawnGrow_Spawn(spawnpoint + (stalker_mins + stalker_maxs), radius, radius * 2.f); diff --git a/src/monsters/m_widow2.cpp b/src/monsters/m_widow2.cpp index 880b763..02b1eb9 100644 --- a/src/monsters/m_widow2.cpp +++ b/src/monsters/m_widow2.cpp @@ -146,7 +146,7 @@ static void Widow2Spawn(gentity_t *self) { startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); - if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) { + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64, true, self->gravityVector)) { ent = CreateGroundMonster(spawnpoint, self->s.angles, stalker_mins, stalker_maxs, "monster_stalker", 256); if (!ent) @@ -199,7 +199,7 @@ static void widow2_ready_spawn(gentity_t *self) { for (i = 0; i < 2; i++) { offset = spawnpoints[i]; startpoint = G_ProjectSource2(self->s.origin, offset, f, r, u); - if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64)) { + if (FindSpawnPoint(startpoint, stalker_mins, stalker_maxs, spawnpoint, 64, true, self->gravityVector)) { float radius = (stalker_maxs - stalker_mins).length() * 0.5f; SpawnGrow_Spawn(spawnpoint + (stalker_mins + stalker_maxs), radius, radius * 2.f); diff --git a/tests/spawn_gravity_tests.cpp b/tests/spawn_gravity_tests.cpp new file mode 100644 index 0000000..1036328 --- /dev/null +++ b/tests/spawn_gravity_tests.cpp @@ -0,0 +1,198 @@ +#include "../src/g_local.h" +#include + +// Limit g_monster_spawn.cpp surface area for unit tests +#define MONSTER_SPAWN_TESTS +#include "../src/g_monster_spawn.cpp" + +// Stub globals expected by the included code +local_game_import_t gi{}; +gentity_t *world = reinterpret_cast(0x1); + +static int g_trace_calls = 0; +static vec3_t g_last_trace_start; +static vec3_t g_last_trace_end; +static vec3_t g_last_bottom_fast; +static vec3_t g_last_bottom_slow; + +/* +============= +TestTrace + +Provides deterministic trace behavior for spawn validation tests. +============= +*/ +static trace_t TestTrace(const vec3_t &start, const vec3_t &, const vec3_t &, const vec3_t &end, gentity_t *, contents_t) +{ + trace_t tr{}; + + g_last_trace_start = start; + g_last_trace_end = end; + g_trace_calls++; + + tr.ent = world; + tr.endpos = end; + tr.fraction = 0.5f; + tr.startsolid = false; + tr.allsolid = false; + + return tr; +} + +/* +============= +G_FixStuckObject_Generic + +Trivial stub to satisfy FindSpawnPoint dependency. +============= +*/ +stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &, const vec3_t &, std::function) +{ + (void)origin; + return stuck_result_t::GOOD_POSITION; +} + +/* +============= +M_CheckBottom_Fast_Generic + +Records the provided gravity vector for verification. +============= +*/ +bool M_CheckBottom_Fast_Generic(const vec3_t &, const vec3_t &, const vec3_t &gravityVector) +{ + g_last_bottom_fast = gravityVector; + return false; +} + +/* +============= +M_CheckBottom_Slow_Generic + +Records the provided gravity vector for verification. +============= +*/ +bool M_CheckBottom_Slow_Generic(const vec3_t &, const vec3_t &, const vec3_t &, gentity_t *, contents_t, const vec3_t &gravityVector, bool) +{ + g_last_bottom_slow = gravityVector; + return true; +} + +/* +============= +M_droptofloor_generic + +Drops an origin along the provided gravity vector until contact is made or a blocking volume is found. +============= +*/ +bool M_droptofloor_generic(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector, gentity_t *ignore, contents_t mask, bool allow_partial) +{ + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + trace_t trace = gi.trace(origin, mins, maxs, origin, ignore, mask); + + if (trace.startsolid) + origin -= gravity_dir; + + vec3_t end = origin + (gravity_dir * 256.0f); + + trace = gi.trace(origin, mins, maxs, end, ignore, mask); + + if (trace.fraction == 1 || trace.allsolid || (!allow_partial && trace.startsolid)) + return false; + + origin = trace.endpos; + + return true; +} + +/* +============= +TestCeilingDropUsesGravity + +Verifies FindSpawnPoint drops along positive gravity vectors. +============= +*/ +static void TestCeilingDropUsesGravity() +{ + gi.trace = TestTrace; + g_trace_calls = 0; + + vec3_t start{ 0.0f, 0.0f, 0.0f }; + vec3_t mins{ -1.0f, -1.0f, -1.0f }; + vec3_t maxs{ 1.0f, 1.0f, 1.0f }; + vec3_t spawn{}; + + bool found = FindSpawnPoint(start, mins, maxs, spawn, 32.0f, true, { 0.0f, 0.0f, 1.0f }); + + assert(found); + assert(g_trace_calls >= 2); + assert(spawn[0] == 0.0f && spawn[1] == 0.0f && spawn[2] == 256.0f); + assert(g_last_trace_end[2] == 256.0f); +} + +/* +============= +TestWallGravityProjectsSpawnVolume + +Ensures CheckSpawnPoint traces along the supplied gravity vector. +============= +*/ +static void TestWallGravityProjectsSpawnVolume() +{ + gi.trace = TestTrace; + g_trace_calls = 0; + + vec3_t origin{ 5.0f, 5.0f, 5.0f }; + vec3_t mins{ -2.0f, -2.0f, -2.0f }; + vec3_t maxs{ 2.0f, 2.0f, 2.0f }; + vec3_t gravity{ 1.0f, 0.0f, 0.0f }; + + bool clear = CheckSpawnPoint(origin, mins, maxs, gravity); + + assert(clear); + assert(g_trace_calls >= 2); + vec3_t delta = g_last_trace_end - g_last_trace_start; + assert(delta[0] == gravity[0] && delta[1] == gravity[1] && delta[2] == gravity[2]); +} + +/* +============= +TestGroundChecksUseGravityVector + +Confirms bottom checks receive the supplied gravity vector. +============= +*/ +static void TestGroundChecksUseGravityVector() +{ + gi.trace = TestTrace; + g_last_bottom_fast = { 0.0f, 0.0f, 0.0f }; + g_last_bottom_slow = { 0.0f, 0.0f, 0.0f }; + + vec3_t origin{ 0.0f, 0.0f, 0.0f }; + vec3_t mins{ -8.0f, -8.0f, -8.0f }; + vec3_t maxs{ 8.0f, 8.0f, 8.0f }; + vec3_t gravity{ 0.0f, 1.0f, 0.0f }; + + bool grounded = CheckGroundSpawnPoint(origin, mins, maxs, 128.0f, gravity); + + assert(grounded); + assert(g_last_bottom_fast == gravity); + assert(g_last_bottom_slow == gravity); +} + +/* +============= +main +============= +*/ +int main() +{ + TestCeilingDropUsesGravity(); + TestWallGravityProjectsSpawnVolume(); + TestGroundChecksUseGravityVector(); + return 0; +} From 7a50398411c2e4fb5d74cb4a12bffeef407a52fe Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 10:26:23 +0000 Subject: [PATCH 064/142] Fix Windows build errors --- src/g_chase.cpp | 4 ++-- src/g_items.cpp | 2 +- src/g_main.cpp | 2 +- src/g_phys.cpp | 6 ++++-- src/g_save.cpp | 4 ---- src/g_utils.cpp | 6 +++++- src/monsters/m_carrier.cpp | 24 +++++++++++++++++------- 7 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/g_chase.cpp b/src/g_chase.cpp index 0aad04f..db36486 100644 --- a/src/g_chase.cpp +++ b/src/g_chase.cpp @@ -29,8 +29,8 @@ void FreeFollower(gentity_t *ent) { ent->client->ps.screen_blend = {}; ent->client->ps.damage_blend = {}; ent->client->ps.rdflags = RDF_NONE; - ent->s.effects = 0; - ent->s.renderfx = 0; + ent->s.effects = EF_NONE; + ent->s.renderfx = RF_NONE; } /* diff --git a/src/g_items.cpp b/src/g_items.cpp index 6724c6e..1c47b6b 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -1860,7 +1860,7 @@ static bool Pickup_Doppelganger(gentity_t *ent, gentity_t *other) { max_allowed = G_GetHoldableMax(g_dm_holdable_doppel_max->integer, ent->item->quantity_max, 1); quantity = other->client->pers.inventory[ent->item->id]; if (quantity >= max_allowed) { - gi.cprintf(other, PRINT_LOW, "You can only carry %d %s\n", max_allowed, ent->item->pickup_name); + gi.Client_Print(other, PRINT_LOW, "You can only carry %d %s\n", max_allowed, ent->item->pickup_name); return false; } diff --git a/src/g_main.cpp b/src/g_main.cpp index 85f0ced..b06af49 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -1067,7 +1067,7 @@ g_dm_item_respawn_rate = gi.cvar("g_dm_item_respawn_rate", "1.0", CVAR_NOFLAGS); game = {}; - const int32_t clamped_maxclients = std::clamp(maxclients->integer, 1, MAX_CLIENTS); + const int32_t clamped_maxclients = std::clamp(maxclients->integer, 1, static_cast(MAX_CLIENTS)); // initialize all entities for this game game.maxentities = maxentities->integer; diff --git a/src/g_phys.cpp b/src/g_phys.cpp index 1b549bd..c8a8e20 100644 --- a/src/g_phys.cpp +++ b/src/g_phys.cpp @@ -2,6 +2,8 @@ // Licensed under the GNU General Public License 2.0. // g_phys.c +#include + #include "g_runthink.h" /* @@ -390,11 +392,11 @@ static bool G_Push(gentity_t *pusher, vec3_t &move, vec3_t &amove) { if (already_touched) continue; - if (num_touched < lengthof(touched)) + if (num_touched < std::size(touched)) touched[num_touched++] = p->ent; G_TouchTriggers(p->ent); - } + } return true; } diff --git a/src/g_save.cpp b/src/g_save.cpp index e9cccf5..cb425f6 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -68,10 +68,6 @@ Initializes the save data lists and validates for duplicates. ============= */ void InitSave() { -#ifndef NDEBUG - json_run_stack_tests(); -#endif - if (save_data_initialized) return; diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 42c2355..d762749 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -102,7 +102,11 @@ gentity_t *G_PickTarget(const char *targetname) { return nullptr; } - return G_SelectRandomTarget(choices, irandom); + return G_SelectRandomTarget( + choices, + [](size_t max_index) { + return static_cast(irandom(static_cast(max_index))); + }); } static THINK(Think_Delay) (gentity_t *ent) -> void { diff --git a/src/monsters/m_carrier.cpp b/src/monsters/m_carrier.cpp index cb9b3b2..833cb2b 100644 --- a/src/monsters/m_carrier.cpp +++ b/src/monsters/m_carrier.cpp @@ -53,11 +53,21 @@ void carrier_dead(gentity_t *self); void carrier_attack_mg(gentity_t *self); void carrier_reattack_mg(gentity_t *self); +static void CarrierCoopCheck(gentity_t *self); + void carrier_attack_gren(gentity_t *self); void carrier_reattack_gren(gentity_t *self); void carrier_start_spawn(gentity_t *self); void carrier_spawn_check(gentity_t *self); + +/* +============= +carrier_prep_spawn + +Prepares the carrier for spawning reinforcements. +============= +*/ void carrier_prep_spawn(gentity_t *self) { CarrierCoopCheck(self); self->monsterinfo.aiflags |= AI_MANUAL_STEERING; @@ -77,6 +87,13 @@ MONSTERINFO_SIGHT(carrier_sight) (gentity_t *self, gentity_t *other) -> void { // // if there is a player behind/below the carrier, and we can shoot, and we can trace a LOS to them .. // pick one of the group, and let it rip +/* +============= +CarrierCoopCheck + +Checks cooperative players behind or below the carrier for rocket targeting. +============= +*/ static void CarrierCoopCheck(gentity_t *self) { // no more than 8 players in coop, so.. std::array targets; @@ -357,13 +374,6 @@ static void CarrierSpawn(gentity_t *self) { } } -void carrier_prep_spawn(gentity_t *self) { - CarrierCoopCheck(self); - self->monsterinfo.aiflags |= AI_MANUAL_STEERING; - self->timestamp = level.time; - self->yaw_speed = 10; - if (FindSpawnPoint(startpoint, reinforcement.mins, reinforcement.maxs, spawnpoint, 32, false, self->gravityVector)) { - void carrier_spawn_check(gentity_t *self) { CarrierCoopCheck(self); CarrierSpawn(self); From 9eac6a86c6bfa7a490b49bd450140f91a57f0360 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 10:29:40 +0000 Subject: [PATCH 065/142] cleaning up --- src/g_cmds.cpp | 2 - src/g_items.cpp | 5806 ++++++++++++++++++++++++----------------------- src/g_utils.cpp | 214 +- 3 files changed, 3045 insertions(+), 2977 deletions(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 638cfb1..9695994 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -3022,11 +3022,9 @@ void G_RevertVote(gclient_t *client) { if (client->pers.voted == 1) { level.vote_yes--; client->pers.voted = 0; - //trap_SetConfigstring(CS_VOTE_YES, va("%i", level.vote_yes)); } else if (client->pers.voted == -1) { level.vote_no--; client->pers.voted = 0; - //trap_SetConfigstring(CS_VOTE_NO, va("%i", level.vote_no)); } } diff --git a/src/g_items.cpp b/src/g_items.cpp index 1c47b6b..72d0d88 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -5,45 +5,45 @@ #include "monsters/m_player.h" //doppelganger #include "g_items_limits.h" -bool Pickup_Weapon(gentity_t *ent, gentity_t *other); -void Use_Weapon(gentity_t *ent, gitem_t *inv); -void Drop_Weapon(gentity_t *ent, gitem_t *inv); - -void Weapon_Blaster(gentity_t *ent); -void Weapon_Shotgun(gentity_t *ent); -void Weapon_SuperShotgun(gentity_t *ent); -void Weapon_Machinegun(gentity_t *ent); -void Weapon_Chaingun(gentity_t *ent); -void Weapon_HyperBlaster(gentity_t *ent); -void Weapon_RocketLauncher(gentity_t *ent); -void Weapon_HandGrenade(gentity_t *ent); -void Weapon_GrenadeLauncher(gentity_t *ent); -void Weapon_Railgun(gentity_t *ent); -void Weapon_BFG(gentity_t *ent); -void Weapon_IonRipper(gentity_t *ent); -void Weapon_Phalanx(gentity_t *ent); -void Weapon_Trap(gentity_t *ent); -void Weapon_ChainFist(gentity_t *ent); -void Weapon_Disruptor(gentity_t *ent); -void Weapon_ETF_Rifle(gentity_t *ent); -void Weapon_PlasmaBeam(gentity_t *ent); -void Weapon_Tesla(gentity_t *ent); -void Weapon_ProxLauncher(gentity_t *ent); - -void Use_Quad(gentity_t *ent, gitem_t *item); +bool Pickup_Weapon(gentity_t* ent, gentity_t* other); +void Use_Weapon(gentity_t* ent, gitem_t* inv); +void Drop_Weapon(gentity_t* ent, gitem_t* inv); + +void Weapon_Blaster(gentity_t* ent); +void Weapon_Shotgun(gentity_t* ent); +void Weapon_SuperShotgun(gentity_t* ent); +void Weapon_Machinegun(gentity_t* ent); +void Weapon_Chaingun(gentity_t* ent); +void Weapon_HyperBlaster(gentity_t* ent); +void Weapon_RocketLauncher(gentity_t* ent); +void Weapon_HandGrenade(gentity_t* ent); +void Weapon_GrenadeLauncher(gentity_t* ent); +void Weapon_Railgun(gentity_t* ent); +void Weapon_BFG(gentity_t* ent); +void Weapon_IonRipper(gentity_t* ent); +void Weapon_Phalanx(gentity_t* ent); +void Weapon_Trap(gentity_t* ent); +void Weapon_ChainFist(gentity_t* ent); +void Weapon_Disruptor(gentity_t* ent); +void Weapon_ETF_Rifle(gentity_t* ent); +void Weapon_PlasmaBeam(gentity_t* ent); +void Weapon_Tesla(gentity_t* ent); +void Weapon_ProxLauncher(gentity_t* ent); + +void Use_Quad(gentity_t* ent, gitem_t* item); static gtime_t quad_drop_timeout_hack; -void Use_Haste(gentity_t *ent, gitem_t *item); +void Use_Haste(gentity_t* ent, gitem_t* item); static gtime_t haste_drop_timeout_hack; -void Use_Double(gentity_t *ent, gitem_t *item); +void Use_Double(gentity_t* ent, gitem_t* item); static gtime_t double_drop_timeout_hack; -void Use_Invisibility(gentity_t *ent, gitem_t *item); +void Use_Invisibility(gentity_t* ent, gitem_t* item); static gtime_t invisibility_drop_timeout_hack; -void Use_Protection(gentity_t *ent, gitem_t *item); +void Use_Protection(gentity_t* ent, gitem_t* item); static gtime_t protection_drop_timeout_hack; -void Use_Regeneration(gentity_t *ent, gitem_t *item); +void Use_Regeneration(gentity_t* ent, gitem_t* item); static gtime_t regeneration_drop_timeout_hack; -static void UsedMessage(gentity_t *ent, gitem_t *item) { +static void UsedMessage(gentity_t* ent, gitem_t* item) { if (!ent || !item) return; @@ -57,10 +57,10 @@ static void UsedMessage(gentity_t *ent, gitem_t *item) { // DOPPELGANGER // *************************** -gentity_t *Sphere_Spawn(gentity_t *owner, spawnflags_t spawnflags); +gentity_t* Sphere_Spawn(gentity_t* owner, spawnflags_t spawnflags); -static DIE(doppelganger_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { - gentity_t *sphere; +static DIE(doppelganger_die) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void { + gentity_t* sphere; float dist; vec3_t dir; @@ -72,7 +72,8 @@ static DIE(doppelganger_die) (gentity_t *self, gentity_t *inflictor, gentity_t * if (dist > 768) { sphere = Sphere_Spawn(self, SF_SPHERE_HUNTER | SF_DOPPELGANGER); sphere->pain(sphere, attacker, 0, 0, mod); - } else { + } + else { sphere = Sphere_Spawn(self, SF_SPHERE_VENGEANCE | SF_DOPPELGANGER); sphere->pain(sphere, attacker, 0, 0, mod); } @@ -89,15 +90,15 @@ static DIE(doppelganger_die) (gentity_t *self, gentity_t *inflictor, gentity_t * BecomeExplosion1(self); } -static PAIN(doppelganger_pain) (gentity_t *self, gentity_t *other, float kick, int damage, const mod_t &mod) -> void { +static PAIN(doppelganger_pain) (gentity_t* self, gentity_t* other, float kick, int damage, const mod_t& mod) -> void { self->enemy = other; } -static THINK(doppelganger_timeout) (gentity_t *self) -> void { +static THINK(doppelganger_timeout) (gentity_t* self) -> void { doppelganger_die(self, self, self, 9999, self->s.origin, MOD_UNKNOWN); } -static THINK(body_think) (gentity_t *self) -> void { +static THINK(body_think) (gentity_t* self) -> void { float r; if (fabsf(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2) { @@ -108,7 +109,8 @@ static THINK(body_think) (gentity_t *self) -> void { self->timestamp = level.time + 1_sec; } } - } else + } + else M_ChangeYaw(self); if (self->teleport_time <= level.time) { @@ -122,9 +124,9 @@ static THINK(body_think) (gentity_t *self) -> void { self->nextthink = level.time + FRAME_TIME_MS; } -void fire_doppelganger(gentity_t *ent, const vec3_t &start, const vec3_t &aimdir) { - gentity_t *base; - gentity_t *body; +void fire_doppelganger(gentity_t* ent, const vec3_t& start, const vec3_t& aimdir) { + gentity_t* base; + gentity_t* body; vec3_t dir; vec3_t forward, right, up; int number; @@ -182,15 +184,15 @@ void fire_doppelganger(gentity_t *ent, const vec3_t &start, const vec3_t &aimdir //====================================================================== -constexpr gtime_t DEFENDER_LIFESPAN = 10_sec; //30_sec; -constexpr gtime_t HUNTER_LIFESPAN = 10_sec; //30_sec; -constexpr gtime_t VENGEANCE_LIFESPAN = 10_sec; //30_sec; -constexpr gtime_t MINIMUM_FLY_TIME = 10_sec; //15_sec; +constexpr gtime_t DEFENDER_LIFESPAN = 10_sec; //30_sec; +constexpr gtime_t HUNTER_LIFESPAN = 10_sec; //30_sec; +constexpr gtime_t VENGEANCE_LIFESPAN = 10_sec; //30_sec; +constexpr gtime_t MINIMUM_FLY_TIME = 10_sec; //15_sec; -void LookAtKiller(gentity_t *self, gentity_t *inflictor, gentity_t *attacker); +void LookAtKiller(gentity_t* self, gentity_t* inflictor, gentity_t* attacker); -void vengeance_touch(gentity_t *self, gentity_t *other, const trace_t &tr, bool other_touching_self); -void hunter_touch(gentity_t *self, gentity_t *other, const trace_t &tr, bool other_touching_self); +void vengeance_touch(gentity_t* self, gentity_t* other, const trace_t& tr, bool other_touching_self); +void hunter_touch(gentity_t* self, gentity_t* other, const trace_t& tr, bool other_touching_self); // ************************* // General Sphere Code @@ -198,7 +200,7 @@ void hunter_touch(gentity_t *self, gentity_t *other, const trace_t &tr, bool oth // ================= // ================= -static THINK(sphere_think_explode) (gentity_t *self) -> void { +static THINK(sphere_think_explode) (gentity_t* self) -> void { if (self->owner && self->owner->client && !(self->spawnflags & SF_DOPPELGANGER)) { self->owner->client->owned_sphere = nullptr; } @@ -208,14 +210,14 @@ static THINK(sphere_think_explode) (gentity_t *self) -> void { // ================= // sphere_explode // ================= -static DIE(sphere_explode) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { +static DIE(sphere_explode) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void { sphere_think_explode(self); } // ================= // sphere_if_idle_die - if the sphere is not currently attacking, blow up. // ================= -static DIE(sphere_if_idle_die) (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void { +static DIE(sphere_if_idle_die) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int damage, const vec3_t& point, const mod_t& mod) -> void { if (!self->enemy) sphere_think_explode(self); } @@ -224,7 +226,7 @@ static DIE(sphere_if_idle_die) (gentity_t *self, gentity_t *inflictor, gentity_t // Sphere Movement // ************************* -static void sphere_fly(gentity_t *self) { +static void sphere_fly(gentity_t* self) { vec3_t dest, dir; if (level.time >= gtime_t::from_sec(self->wait)) { @@ -247,7 +249,7 @@ static void sphere_fly(gentity_t *self) { self->velocity = dir * 5; } -static void sphere_chase(gentity_t *self, int stupidChase) { +static void sphere_chase(gentity_t* self, int stupidChase) { vec3_t dest; vec3_t dir; float dist; @@ -271,7 +273,8 @@ static void sphere_chase(gentity_t *self, int stupidChase) { self->s.angles = vectoangles(dir); self->velocity = dir * 300; // 500; self->monsterinfo.saved_goal = dest; - } else if (!self->monsterinfo.saved_goal) { + } + else if (!self->monsterinfo.saved_goal) { dir = self->enemy->s.origin - self->s.origin; dist = dir.normalize(); self->s.angles = vectoangles(dir); @@ -279,7 +282,8 @@ static void sphere_chase(gentity_t *self, int stupidChase) { // if lurking, hunter sphere uses lurking sound self->s.sound = gi.soundindex("spheres/h_lurk.wav"); self->velocity = {}; - } else { + } + else { dir = self->monsterinfo.saved_goal - self->s.origin; dist = dir.normalize(); @@ -296,7 +300,8 @@ static void sphere_chase(gentity_t *self, int stupidChase) { // if moving, hunter sphere uses active sound if (!stupidChase) self->s.sound = gi.soundindex("spheres/h_active.wav"); - } else { + } + else { dir = self->enemy->s.origin - self->s.origin; dist = dir.normalize(); self->s.angles = vectoangles(dir); @@ -314,7 +319,7 @@ static void sphere_chase(gentity_t *self, int stupidChase) { // Attack related stuff // ************************* -static void sphere_fire(gentity_t *self, gentity_t *enemy) { +static void sphere_fire(gentity_t* self, gentity_t* enemy) { vec3_t dest; vec3_t dir; @@ -336,7 +341,7 @@ static void sphere_fire(gentity_t *self, gentity_t *enemy) { self->nextthink = gtime_t::from_sec(self->wait); } -static void sphere_touch(gentity_t *self, gentity_t *other, const trace_t &tr, mod_t mod) { +static void sphere_touch(gentity_t* self, gentity_t* other, const trace_t& tr, mod_t mod) { if (self->spawnflags.has(SF_DOPPELGANGER)) { if (other == self->teammaster) return; @@ -344,7 +349,8 @@ static void sphere_touch(gentity_t *self, gentity_t *other, const trace_t &tr, m self->takedamage = false; self->owner = self->teammaster; self->teammaster = nullptr; - } else { + } + else { if (other == self->owner) return; // PMM - don't blow up on bodies @@ -361,7 +367,8 @@ static void sphere_touch(gentity_t *self, gentity_t *other, const trace_t &tr, m if (other->takedamage) { T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal, 10000, 1, DAMAGE_DESTROY_ARMOR, mod); - } else { + } + else { T_RadiusDamage(self, self->owner, 512, self->owner, 256, DAMAGE_NONE, mod); } } @@ -369,15 +376,15 @@ static void sphere_touch(gentity_t *self, gentity_t *other, const trace_t &tr, m sphere_think_explode(self); } -TOUCH(vengeance_touch) (gentity_t *self, gentity_t *other, const trace_t &tr, bool other_touching_self) -> void { +TOUCH(vengeance_touch) (gentity_t* self, gentity_t* other, const trace_t& tr, bool other_touching_self) -> void { if (self->spawnflags.has(SF_DOPPELGANGER)) sphere_touch(self, other, tr, MOD_DOPPEL_VENGEANCE); else sphere_touch(self, other, tr, MOD_VENGEANCE_SPHERE); } -TOUCH(hunter_touch) (gentity_t *self, gentity_t *other, const trace_t &tr, bool other_touching_self) -> void { - gentity_t *owner; +TOUCH(hunter_touch) (gentity_t* self, gentity_t* other, const trace_t& tr, bool other_touching_self) -> void { + gentity_t* owner; // don't blow up if you hit the world.... sheesh. if (other == world) return; @@ -398,7 +405,7 @@ TOUCH(hunter_touch) (gentity_t *self, gentity_t *other, const trace_t &tr, bool sphere_touch(self, other, tr, MOD_HUNTER_SPHERE); } -static void defender_shoot(gentity_t *self, gentity_t *enemy) { +static void defender_shoot(gentity_t* self, gentity_t* enemy) { vec3_t dir; vec3_t start; @@ -431,7 +438,7 @@ static void defender_shoot(gentity_t *self, gentity_t *enemy) { // Activation Related Stuff // ************************* -static void body_gib(gentity_t *self) { +static void body_gib(gentity_t* self) { gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); ThrowGibs(self, 50, { { 4, "models/objects/gibs/sm_meat/tris.md2" }, @@ -439,8 +446,8 @@ static void body_gib(gentity_t *self) { }); } -static PAIN(hunter_pain) (gentity_t *self, gentity_t *other, float kick, int damage, const mod_t &mod) -> void { - gentity_t *owner; +static PAIN(hunter_pain) (gentity_t* self, gentity_t* other, float kick, int damage, const mod_t& mod) -> void { + gentity_t* owner; float dist; vec3_t dir; @@ -455,7 +462,8 @@ static PAIN(hunter_pain) (gentity_t *self, gentity_t *other, float kick, int dam if (other == owner) return; - } else { + } + else { // if fired by a doppelganger, set it to 10 second timeout self->wait = (level.time + MINIMUM_FLY_TIME).seconds(); } @@ -509,14 +517,14 @@ static PAIN(hunter_pain) (gentity_t *self, gentity_t *other, float kick, int dam } } -static PAIN(defender_pain) (gentity_t *self, gentity_t *other, float kick, int damage, const mod_t &mod) -> void { +static PAIN(defender_pain) (gentity_t* self, gentity_t* other, float kick, int damage, const mod_t& mod) -> void { if (other == self->owner) return; self->enemy = other; } -static PAIN(vengeance_pain) (gentity_t *self, gentity_t *other, float kick, int damage, const mod_t &mod) -> void { +static PAIN(vengeance_pain) (gentity_t* self, gentity_t* other, float kick, int damage, const mod_t& mod) -> void { if (self->enemy) return; @@ -526,7 +534,8 @@ static PAIN(vengeance_pain) (gentity_t *self, gentity_t *other, float kick, int if (other == self->owner) return; - } else { + } + else { self->wait = (level.time + MINIMUM_FLY_TIME).seconds(); } @@ -541,7 +550,7 @@ static PAIN(vengeance_pain) (gentity_t *self, gentity_t *other, float kick, int // Think Functions // ************************* -static THINK(defender_think) (gentity_t *self) -> void { +static THINK(defender_think) (gentity_t* self) -> void { if (!self->owner) { G_FreeEntity(self); return; @@ -575,14 +584,14 @@ static THINK(defender_think) (gentity_t *self) -> void { self->nextthink = level.time + 10_hz; } -static THINK(hunter_think) (gentity_t *self) -> void { +static THINK(hunter_think) (gentity_t* self) -> void { // if we've exited the level, just remove ourselves. if (level.intermission_time) { sphere_think_explode(self); return; } - gentity_t *owner = self->owner; + gentity_t* owner = self->owner; if (!owner && !(self->spawnflags & SF_DOPPELGANGER)) { G_FreeEntity(self); @@ -615,21 +624,23 @@ static THINK(hunter_think) (gentity_t *self) -> void { owner->mins = {}; owner->maxs = {}; gi.linkentity(owner); - } else // sphere timed out + } + else // sphere timed out { owner->velocity = {}; owner->movetype = MOVETYPE_NONE; gi.linkentity(owner); } } - } else + } + else sphere_fly(self); if (self->inuse) self->nextthink = level.time + 10_hz; } -static THINK(vengeance_think) (gentity_t *self) -> void { +static THINK(vengeance_think) (gentity_t* self) -> void { // if we've exited the level, just remove ourselves. if (level.intermission_time) { sphere_think_explode(self); @@ -651,8 +662,8 @@ static THINK(vengeance_think) (gentity_t *self) -> void { } // ================= -gentity_t *Sphere_Spawn(gentity_t *owner, spawnflags_t spawnflags) { - gentity_t *sphere; +gentity_t* Sphere_Spawn(gentity_t* owner, spawnflags_t spawnflags) { + gentity_t* sphere; sphere = G_Spawn(); sphere->s.origin = owner->s.origin; @@ -719,7 +730,7 @@ gentity_t *Sphere_Spawn(gentity_t *owner, spawnflags_t spawnflags) { // Own_Sphere - attach the sphere to the client so we can // directly access it later // ================= -static void Own_Sphere(gentity_t *self, gentity_t *sphere) { +static void Own_Sphere(gentity_t* self, gentity_t* sphere) { if (!sphere) return; @@ -734,29 +745,30 @@ static void Own_Sphere(gentity_t *self, gentity_t *sphere) { if (self->client->owned_sphere->inuse) { G_FreeEntity(self->client->owned_sphere); self->client->owned_sphere = sphere; - } else { + } + else { self->client->owned_sphere = sphere; } } } } -void Defender_Launch(gentity_t *self) { - gentity_t *sphere; +void Defender_Launch(gentity_t* self) { + gentity_t* sphere; sphere = Sphere_Spawn(self, SF_SPHERE_DEFENDER); Own_Sphere(self, sphere); } -void Hunter_Launch(gentity_t *self) { - gentity_t *sphere; +void Hunter_Launch(gentity_t* self) { + gentity_t* sphere; sphere = Sphere_Spawn(self, SF_SPHERE_HUNTER); Own_Sphere(self, sphere); } -void Vengeance_Launch(gentity_t *self) { - gentity_t *sphere; +void Vengeance_Launch(gentity_t* self) { + gentity_t* sphere; sphere = Sphere_Spawn(self, SF_SPHERE_VENGEANCE); Own_Sphere(self, sphere); @@ -764,12 +776,12 @@ void Vengeance_Launch(gentity_t *self) { //====================================================================== -static gentity_t *QuadHog_FindSpawn() { +static gentity_t* QuadHog_FindSpawn() { return SelectDeathmatchSpawnPoint(nullptr, vec3_origin, SPAWN_FAR_HALF, true, true, false, true).spot; } static void QuadHod_ClearAll() { - gentity_t *ent; + gentity_t* ent; for (ent = g_entities; ent < &g_entities[globals.num_entities]; ent++) { @@ -795,8 +807,8 @@ static void QuadHod_ClearAll() { } } -void QuadHog_Spawn(gitem_t *item, gentity_t *spot, bool reset) { - gentity_t *ent; +void QuadHog_Spawn(gitem_t* item, gentity_t* spot, bool reset) { + gentity_t* ent; vec3_t forward, right; vec3_t angles = vec3_origin; @@ -835,9 +847,9 @@ void QuadHog_Spawn(gitem_t *item, gentity_t *spot, bool reset) { gi.linkentity(ent); } -THINK(QuadHog_DoSpawn) (gentity_t *ent) -> void { - gentity_t *spot; - gitem_t *it = GetItemByIndex(IT_POWERUP_QUAD); +THINK(QuadHog_DoSpawn) (gentity_t* ent) -> void { + gentity_t* spot; + gitem_t* it = GetItemByIndex(IT_POWERUP_QUAD); if (!it) return; @@ -849,9 +861,9 @@ THINK(QuadHog_DoSpawn) (gentity_t *ent) -> void { G_FreeEntity(ent); } -THINK(QuadHog_DoReset) (gentity_t *ent) -> void { - gentity_t *spot; - gitem_t *it = GetItemByIndex(IT_POWERUP_QUAD); +THINK(QuadHog_DoReset) (gentity_t* ent) -> void { + gentity_t* spot; + gitem_t* it = GetItemByIndex(IT_POWERUP_QUAD); if (!it) return; @@ -864,7 +876,7 @@ THINK(QuadHog_DoReset) (gentity_t *ent) -> void { } void QuadHog_SetupSpawn(gtime_t delay) { - gentity_t *ent; + gentity_t* ent; if (!g_quadhog->integer) return; @@ -882,7 +894,7 @@ void QuadHog_SetupSpawn(gtime_t delay) { constexpr gtime_t TECH_TIMEOUT = 60_sec; // seconds before techs spawn again -static bool Tech_PlayerHasATech(gentity_t *ent) { +static bool Tech_PlayerHasATech(gentity_t* ent) { if (Tech_Held(ent) != nullptr) { if (level.time - ent->client->tech_last_message_time > 5_sec) { gi.LocCenter_Print(ent, "$g_already_have_tech"); @@ -893,7 +905,7 @@ static bool Tech_PlayerHasATech(gentity_t *ent) { return false; } -gitem_t *Tech_Held(gentity_t *ent) { +gitem_t* Tech_Held(gentity_t* ent) { for (size_t i = 0; i < q_countof(tech_ids); i++) { if (ent->client->pers.inventory[tech_ids[i]]) return GetItemByIndex(tech_ids[i]); @@ -901,7 +913,7 @@ gitem_t *Tech_Held(gentity_t *ent) { return nullptr; } -static bool Tech_Pickup(gentity_t *ent, gentity_t *other) { +static bool Tech_Pickup(gentity_t* ent, gentity_t* other) { // client only gets one tech if (Tech_PlayerHasATech(other)) return false; @@ -911,32 +923,33 @@ static bool Tech_Pickup(gentity_t *ent, gentity_t *other) { return true; } -static void Tech_Spawn(gitem_t *item, gentity_t *spot); +static void Tech_Spawn(gitem_t* item, gentity_t* spot); -static gentity_t *FindTechSpawn() { +static gentity_t* FindTechSpawn() { return SelectDeathmatchSpawnPoint(nullptr, vec3_origin, SPAWN_FAR_HALF, true, true, false, true).spot; } -static THINK(Tech_Think) (gentity_t *tech) -> void { - gentity_t *spot; +static THINK(Tech_Think) (gentity_t* tech) -> void { + gentity_t* spot; if ((spot = FindTechSpawn()) != nullptr) { Tech_Spawn(tech->item, spot); G_FreeEntity(tech); - } else { + } + else { tech->nextthink = level.time + TECH_TIMEOUT; tech->think = Tech_Think; } } -static THINK(Tech_Make_Touchable) (gentity_t *tech) -> void { +static THINK(Tech_Make_Touchable) (gentity_t* tech) -> void { tech->touch = Touch_Item; tech->nextthink = level.time + TECH_TIMEOUT; tech->think = Tech_Think; } -static void Tech_Drop(gentity_t *ent, gitem_t *item) { - gentity_t *tech; +static void Tech_Drop(gentity_t* ent, gitem_t* item) { + gentity_t* tech; tech = Drop_Item(ent, item); tech->nextthink = level.time + 1_sec; @@ -944,8 +957,8 @@ static void Tech_Drop(gentity_t *ent, gitem_t *item) { ent->client->pers.inventory[item->id] = 0; } -void Tech_DeadDrop(gentity_t *ent) { - gentity_t *dropped; +void Tech_DeadDrop(gentity_t* ent) { + gentity_t* dropped; int i; i = 0; @@ -963,8 +976,8 @@ void Tech_DeadDrop(gentity_t *ent) { } } -static void Tech_Spawn(gitem_t *item, gentity_t *spot) { - gentity_t *ent = G_Spawn(); +static void Tech_Spawn(gitem_t* item, gentity_t* spot) { + gentity_t* ent = G_Spawn(); vec3_t forward, right; vec3_t angles = { 0, (float)irandom(360), 0 }; @@ -1000,8 +1013,8 @@ static bool AllowTechs() { return !!(g_allow_techs->integer && ItemSpawnsEnabled()); } -static THINK(Tech_SpawnAll) (gentity_t *ent) -> void { - gentity_t *spot; +static THINK(Tech_SpawnAll) (gentity_t* ent) -> void { + gentity_t* spot; if (!AllowTechs()) return; @@ -1015,7 +1028,7 @@ static THINK(Tech_SpawnAll) (gentity_t *ent) -> void { if (!num) return; - gitem_t *it = nullptr; + gitem_t* it = nullptr; for (size_t i = 0; i < q_countof(tech_ids); i++) { it = GetItemByIndex(tech_ids[i]); if (!it) @@ -1032,13 +1045,13 @@ void Tech_SetupSpawn() { if (!AllowTechs()) return; - gentity_t *ent = G_Spawn(); + gentity_t* ent = G_Spawn(); ent->nextthink = level.time + 2_sec; ent->think = Tech_SpawnAll; } void Tech_Reset() { - gentity_t *ent; + gentity_t* ent; uint32_t i; for (ent = g_entities + 1, i = 1; i < globals.num_entities; i++, ent++) { @@ -1050,7 +1063,7 @@ void Tech_Reset() { //Tech_SpawnAll(nullptr); } -int Tech_ApplyDisruptorShield(gentity_t *ent, int dmg) { +int Tech_ApplyDisruptorShield(gentity_t* ent, int dmg) { float volume = 1.0; if (ent->client && ent->client->silencer_shots) @@ -1064,14 +1077,14 @@ int Tech_ApplyDisruptorShield(gentity_t *ent, int dmg) { return dmg; } -int Tech_ApplyPowerAmp(gentity_t *ent, int dmg) { +int Tech_ApplyPowerAmp(gentity_t* ent, int dmg) { if (dmg && ent->client && ent->client->pers.inventory[IT_TECH_POWER_AMP]) { return dmg * 2; } return dmg; } -bool Tech_ApplyPowerAmpSound(gentity_t *ent) { +bool Tech_ApplyPowerAmpSound(gentity_t* ent) { float volume = 1.0; if (ent->client && ent->client->silencer_shots) @@ -1091,14 +1104,14 @@ bool Tech_ApplyPowerAmpSound(gentity_t *ent) { return false; } -bool Tech_ApplyTimeAccel(gentity_t *ent) { +bool Tech_ApplyTimeAccel(gentity_t* ent) { if (ent->client && ent->client->pers.inventory[IT_TECH_TIME_ACCEL]) return true; return false; } -void Tech_ApplyTimeAccelSound(gentity_t *ent) { +void Tech_ApplyTimeAccelSound(gentity_t* ent) { float volume = 1.0; if (ent->client && ent->client->silencer_shots) @@ -1112,14 +1125,14 @@ void Tech_ApplyTimeAccelSound(gentity_t *ent) { } } -void Tech_ApplyAutoDoc(gentity_t *ent) { +void Tech_ApplyAutoDoc(gentity_t* ent) { bool noise = false; - gclient_t *cl; + gclient_t* cl; int index; float volume = 1.0; bool mod = g_instagib->integer || g_nadefest->integer; bool no_health = mod || GTF(GTF_ARENA) || g_no_health->integer; - int max = g_vampiric_damage->integer ? ceil(g_vampiric_health_max->integer/2) : mod ? 100 : 150; + int max = g_vampiric_damage->integer ? ceil(g_vampiric_health_max->integer / 2) : mod ? 100 : 150; cl = ent->client; if (!cl) @@ -1171,7 +1184,7 @@ void Tech_ApplyAutoDoc(gentity_t *ent) { } } -bool Tech_HasRegeneration(gentity_t *ent) { +bool Tech_HasRegeneration(gentity_t* ent) { if (!ent->client) return false; if (ent->client->pers.inventory[IT_TECH_AUTODOC]) return true; if (g_instagib->integer) return true; @@ -1186,22 +1199,22 @@ bool Tech_HasRegeneration(gentity_t *ent) { GetItemByIndex =============== */ -gitem_t *GetItemByIndex(item_id_t index) { +gitem_t* GetItemByIndex(item_id_t index) { if (index <= IT_NULL || index >= IT_TOTAL) return nullptr; return &itemlist[index]; } -static gitem_t *ammolist[AMMO_MAX]; +static gitem_t* ammolist[AMMO_MAX]; -gitem_t *GetItemByAmmo(ammo_t ammo) { +gitem_t* GetItemByAmmo(ammo_t ammo) { return ammolist[ammo]; } -static gitem_t *poweruplist[POWERUP_MAX]; +static gitem_t* poweruplist[POWERUP_MAX]; -gitem_t *GetItemByPowerup(powerup_t powerup) { +gitem_t* GetItemByPowerup(powerup_t powerup) { return poweruplist[powerup]; } @@ -1211,9 +1224,9 @@ FindItemByClassname =============== */ -gitem_t *FindItemByClassname(const char *classname) { +gitem_t* FindItemByClassname(const char* classname) { int i; - gitem_t *it; + gitem_t* it; it = itemlist; for (i = 0; i < IT_TOTAL; i++, it++) { @@ -1232,9 +1245,9 @@ FindItem =============== */ -gitem_t *FindItem(const char *pickup_name) { +gitem_t* FindItem(const char* pickup_name) { int i; - gitem_t *it; + gitem_t* it; it = itemlist; for (i = 0; i < IT_TOTAL; i++, it++) { @@ -1250,7 +1263,7 @@ gitem_t *FindItem(const char *pickup_name) { //====================================================================== static inline item_flags_t GetSubstituteItemFlags(item_id_t id) { - const gitem_t *item = GetItemByIndex(id); + const gitem_t* item = GetItemByIndex(id); // we want to stay within the item class item_flags_t flags = item->flags & IF_TYPE_MASK; @@ -1261,7 +1274,7 @@ static inline item_flags_t GetSubstituteItemFlags(item_id_t id) { return flags; } -static inline item_id_t FindSubstituteItem(gentity_t *ent) { +static inline item_id_t FindSubstituteItem(gentity_t* ent) { // never replace flags if (ent->item->id == IT_FLAG_RED || ent->item->id == IT_FLAG_BLUE || ent->item->id == IT_TAG_TOKEN) return IT_NULL; @@ -1324,26 +1337,31 @@ static inline item_id_t FindSubstituteItem(gentity_t *ent) { // gather matching items for (item_id_t i = static_cast(IT_NULL + 1); i < IT_TOTAL; i = static_cast(static_cast(i) + 1)) { - const gitem_t *it = GetItemByIndex(i); + const gitem_t* it = GetItemByIndex(i); item_flags_t itflags = it->flags; bool add = false, subtract = false; if (game.item_inhibit_pu && itflags & (IF_POWERUP | IF_SPHERE)) { add = game.item_inhibit_pu > 0 ? true : false; subtract = game.item_inhibit_pu < 0 ? true : false; - } else if (game.item_inhibit_pa && itflags & IF_POWER_ARMOR) { + } + else if (game.item_inhibit_pa && itflags & IF_POWER_ARMOR) { add = game.item_inhibit_pa > 0 ? true : false; subtract = game.item_inhibit_pa < 0 ? true : false; - } else if (game.item_inhibit_ht && itflags & IF_HEALTH) { + } + else if (game.item_inhibit_ht && itflags & IF_HEALTH) { add = game.item_inhibit_ht > 0 ? true : false; subtract = game.item_inhibit_ht < 0 ? true : false; - } else if (game.item_inhibit_ar && itflags & IF_ARMOR) { + } + else if (game.item_inhibit_ar && itflags & IF_ARMOR) { add = game.item_inhibit_ar > 0 ? true : false; subtract = game.item_inhibit_ar < 0 ? true : false; - } else if (game.item_inhibit_am && itflags & IF_AMMO) { + } + else if (game.item_inhibit_am && itflags & IF_AMMO) { add = game.item_inhibit_am > 0 ? true : false; subtract = game.item_inhibit_am < 0 ? true : false; - } else if (game.item_inhibit_wp && itflags & IF_WEAPON) { + } + else if (game.item_inhibit_wp && itflags & IF_WEAPON) { add = game.item_inhibit_wp > 0 ? true : false; subtract = game.item_inhibit_wp < 0 ? true : false; } @@ -1388,7 +1406,7 @@ static inline item_id_t FindSubstituteItem(gentity_t *ent) { return possible_items[irandom(possible_item_count)]; } -item_id_t DoRandomRespawn(gentity_t *ent) { +item_id_t DoRandomRespawn(gentity_t* ent) { if (!ent->item) return IT_NULL; // why @@ -1401,11 +1419,11 @@ item_id_t DoRandomRespawn(gentity_t *ent) { } // originally 'DoRespawn' -THINK(RespawnItem) (gentity_t *ent) -> void { +THINK(RespawnItem) (gentity_t* ent) -> void { if (ent->team) { - gentity_t *master, *current; + gentity_t* master, * current; int count, choice; - + if (!ent->teammaster) gi.Com_ErrorFmt("{}: {}: bad teammaster", __FUNCTION__, *ent); @@ -1429,13 +1447,14 @@ THINK(RespawnItem) (gentity_t *ent) -> void { if (ent == current) current_index = count; } - + if (RS(RS_MM)) { choice = (current_index + 1) % count; //gi.Com_PrintFmt("ci={} co={} ch={}\n", current_index, count, choice); for (count = 0, ent = master; count < choice; ent = ent->chain, count++) ; - } else { + } + else { choice = irandom(count); for (count = 0, ent = master; count < choice; ent = ent->chain, count++) ; @@ -1477,7 +1496,7 @@ THINK(RespawnItem) (gentity_t *ent) -> void { } } -void SetRespawn(gentity_t *ent, gtime_t delay, bool hide_self) { +void SetRespawn(gentity_t* ent, gtime_t delay, bool hide_self) { if (!deathmatch->integer) return; @@ -1512,15 +1531,15 @@ void SetRespawn(gentity_t *ent, gtime_t delay, bool hide_self) { // 4x longer delay in horde if (GT(GT_HORDE)) - ent->nextthink += delay*3; + ent->nextthink += delay * 3; ent->think = RespawnItem; } //====================================================================== -static void Use_Teleporter(gentity_t *ent, gitem_t *item) { - gentity_t *fx = G_Spawn(); +static void Use_Teleporter(gentity_t* ent, gitem_t* item) { + gentity_t* fx = G_Spawn(); fx->classname = "telefx"; fx->s.event = EV_PLAYER_TELEPORT; fx->s.origin = ent->s.origin; @@ -1536,7 +1555,7 @@ static void Use_Teleporter(gentity_t *ent, gitem_t *item) { UsedMessage(ent, item); } -static bool Pickup_Teleporter(gentity_t *ent, gentity_t *other) { +static bool Pickup_Teleporter(gentity_t* ent, gentity_t* other) { if (!deathmatch->integer) return false; if (other->client->pers.inventory[ent->item->id]) @@ -1559,7 +1578,7 @@ static bool IsInstantItemsEnabled() { return false; } -static bool Pickup_AllowPowerupPickup(gentity_t *ent, gentity_t *other) { +static bool Pickup_AllowPowerupPickup(gentity_t* ent, gentity_t* other) { int quantity = other->client->pers.inventory[ent->item->id]; if ((skill->integer == 0 && quantity >= 4) || (skill->integer == 1 && quantity >= 3) || @@ -1587,19 +1606,19 @@ static bool Pickup_AllowPowerupPickup(gentity_t *ent, gentity_t *other) { return true; } -static bool Pickup_Powerup(gentity_t *ent, gentity_t *other) { +static bool Pickup_Powerup(gentity_t* ent, gentity_t* other) { if (!Pickup_AllowPowerupPickup(ent, other)) return false; other->client->pers.inventory[ent->item->id]++; - + if (g_quadhog->integer && ent->item->id == IT_POWERUP_QUAD) { if (ent->item->use) ent->item->use(other, ent->item); G_FreeEntity(ent); return true; } - + bool is_dropped_from_death = ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED_PLAYER) && !ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED); if (IsInstantItemsEnabled() || is_dropped_from_death) { @@ -1666,7 +1685,7 @@ static bool Pickup_Powerup(gentity_t *ent, gentity_t *other) { return true; } -static bool Pickup_AllowTimedItemPickup(gentity_t *ent, gentity_t *other) { +static bool Pickup_AllowTimedItemPickup(gentity_t* ent, gentity_t* other) { int quantity = other->client->pers.inventory[ent->item->id]; if ((skill->integer == 0 && quantity >= 3) || (skill->integer == 1 && quantity >= 2) || @@ -1679,7 +1698,7 @@ static bool Pickup_AllowTimedItemPickup(gentity_t *ent, gentity_t *other) { return true; } -static bool Pickup_TimedItem(gentity_t *ent, gentity_t *other) { +static bool Pickup_TimedItem(gentity_t* ent, gentity_t* other) { if (!Pickup_AllowTimedItemPickup(ent, other)) return false; @@ -1693,9 +1712,11 @@ static bool Pickup_TimedItem(gentity_t *ent, gentity_t *other) { bool msg = false; if (ent->item->id == IT_ADRENALINE && !other->client->pers.holdable_item_msg_adren) { other->client->pers.holdable_item_msg_adren = msg = true; - } else if (ent->item->id == IT_TELEPORTER && !other->client->pers.holdable_item_msg_tele) { + } + else if (ent->item->id == IT_TELEPORTER && !other->client->pers.holdable_item_msg_tele) { other->client->pers.holdable_item_msg_tele = msg = true; - } else if (ent->item->id == IT_DOPPELGANGER && !other->client->pers.holdable_item_msg_doppel) { + } + else if (ent->item->id == IT_DOPPELGANGER && !other->client->pers.holdable_item_msg_doppel) { other->client->pers.holdable_item_msg_doppel = msg = true; } if (msg) @@ -1710,7 +1731,7 @@ static bool Pickup_TimedItem(gentity_t *ent, gentity_t *other) { //====================================================================== -static void Use_Defender(gentity_t *ent, gitem_t *item) { +static void Use_Defender(gentity_t* ent, gitem_t* item) { if (ent->client && ent->client->owned_sphere) { gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time"); return; @@ -1721,7 +1742,7 @@ static void Use_Defender(gentity_t *ent, gitem_t *item) { Defender_Launch(ent); } -static void Use_Hunter(gentity_t *ent, gitem_t *item) { +static void Use_Hunter(gentity_t* ent, gitem_t* item) { if (ent->client && ent->client->owned_sphere) { gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time"); return; @@ -1732,7 +1753,7 @@ static void Use_Hunter(gentity_t *ent, gitem_t *item) { Hunter_Launch(ent); } -static void Use_Vengeance(gentity_t *ent, gitem_t *item) { +static void Use_Vengeance(gentity_t* ent, gitem_t* item) { if (ent->client && ent->client->owned_sphere) { gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time"); return; @@ -1743,7 +1764,7 @@ static void Use_Vengeance(gentity_t *ent, gitem_t *item) { Vengeance_Launch(ent); } -static bool Pickup_Sphere(gentity_t *ent, gentity_t *other) { +static bool Pickup_Sphere(gentity_t* ent, gentity_t* other) { int quantity; if (other->client && other->client->owned_sphere) { @@ -1774,7 +1795,7 @@ static bool Pickup_Sphere(gentity_t *ent, gentity_t *other) { //====================================================================== -static void Use_IR(gentity_t *ent, gitem_t *item) { +static void Use_IR(gentity_t* ent, gitem_t* item) { ent->client->pers.inventory[item->id]--; ent->client->ir_time = max(level.time, ent->client->ir_time) + 60_sec; @@ -1784,7 +1805,7 @@ static void Use_IR(gentity_t *ent, gitem_t *item) { //====================================================================== -static void Use_Nuke(gentity_t *ent, gitem_t *item) { +static void Use_Nuke(gentity_t* ent, gitem_t* item) { vec3_t forward, right, start; ent->client->pers.inventory[item->id]--; @@ -1795,7 +1816,7 @@ static void Use_Nuke(gentity_t *ent, gitem_t *item) { fire_nuke(ent, start, forward, 100); } -static bool Pickup_Nuke(gentity_t *ent, gentity_t *other) { +static bool Pickup_Nuke(gentity_t* ent, gentity_t* other) { int quantity = other->client->pers.inventory[ent->item->id]; if (quantity >= 1) @@ -1820,7 +1841,7 @@ Use_Doppelganger Spawns a doppelganger at a nearby valid location and consumes the item. ============= */ -static void Use_Doppelganger(gentity_t *ent, gitem_t *item) { +static void Use_Doppelganger(gentity_t* ent, gitem_t* item) { vec3_t forward, right; vec3_t createPt, spawnPt; vec3_t ang; @@ -1850,7 +1871,7 @@ Pickup_Doppelganger Checks for doppelganger limits, granting the pickup when allowed. ============= */ -static bool Pickup_Doppelganger(gentity_t *ent, gentity_t *other) { +static bool Pickup_Doppelganger(gentity_t* ent, gentity_t* other) { int quantity; int max_allowed; @@ -1860,7 +1881,7 @@ static bool Pickup_Doppelganger(gentity_t *ent, gentity_t *other) { max_allowed = G_GetHoldableMax(g_dm_holdable_doppel_max->integer, ent->item->quantity_max, 1); quantity = other->client->pers.inventory[ent->item->id]; if (quantity >= max_allowed) { - gi.Client_Print(other, PRINT_LOW, "You can only carry %d %s\n", max_allowed, ent->item->pickup_name); + gi.LocClient_Print(other, PRINT_LOW, "You can only carry {} {}\n", max_allowed, ent->item->pickup_name); return false; } @@ -1873,7 +1894,7 @@ static bool Pickup_Doppelganger(gentity_t *ent, gentity_t *other) { //====================================================================== -static bool Pickup_General(gentity_t *ent, gentity_t *other) { +static bool Pickup_General(gentity_t* ent, gentity_t* other) { if (other->client->pers.inventory[ent->item->id]) return false; @@ -1884,17 +1905,17 @@ static bool Pickup_General(gentity_t *ent, gentity_t *other) { return true; } -static bool Pickup_Ball(gentity_t *ent, gentity_t *other) { +static bool Pickup_Ball(gentity_t* ent, gentity_t* other) { other->client->pers.inventory[ent->item->id] = 1; return true; } -static void Drop_General(gentity_t *ent, gitem_t *item) { +static void Drop_General(gentity_t* ent, gitem_t* item) { if (g_quadhog->integer && item->id == IT_POWERUP_QUAD) return; - gentity_t *dropped = Drop_Item(ent, item); + gentity_t* dropped = Drop_Item(ent, item); dropped->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; dropped->svflags &= ~SVF_INSTANCED; ent->client->pers.inventory[item->id]--; @@ -1932,7 +1953,7 @@ static void Drop_General(gentity_t *ent, gitem_t *item) { //====================================================================== -static void Use_Adrenaline(gentity_t *ent, gitem_t *item) { +static void Use_Adrenaline(gentity_t* ent, gitem_t* item) { ent->max_health += deathmatch->integer ? ((RS(RS_MM)) ? 5 : 0) : 1; if (ent->health < ent->max_health) @@ -1946,7 +1967,7 @@ static void Use_Adrenaline(gentity_t *ent, gitem_t *item) { UsedMessage(ent, item); } -static bool Pickup_LegacyHead(gentity_t *ent, gentity_t *other) { +static bool Pickup_LegacyHead(gentity_t* ent, gentity_t* other) { other->max_health += 5; other->health += 5; @@ -1955,7 +1976,7 @@ static bool Pickup_LegacyHead(gentity_t *ent, gentity_t *other) { return true; } -void G_CheckPowerArmor(gentity_t *ent) { +void G_CheckPowerArmor(gentity_t* ent) { bool has_enough_cells; if (!ent->client->pers.inventory[IT_AMMO_CELLS]) @@ -1968,11 +1989,12 @@ void G_CheckPowerArmor(gentity_t *ent) { if (ent->flags & FL_POWER_ARMOR) { // ran out of cells for power armor / lost power armor if (!has_enough_cells || (!ent->client->pers.inventory[IT_POWER_SCREEN] && - !ent->client->pers.inventory[IT_POWER_SHIELD])) { + !ent->client->pers.inventory[IT_POWER_SHIELD])) { ent->flags &= ~FL_POWER_ARMOR; gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); } - } else { + } + else { // special case for power armor, for auto-shields if (ent->client->pers.autoshield != AUTO_SHIELD_MANUAL && has_enough_cells && (ent->client->pers.inventory[IT_POWER_SCREEN] || @@ -1999,9 +2021,9 @@ static item_id_t AmmoConvertId(item_id_t original_id) { return new_id; } -static inline bool G_AddAmmoAndCap(gentity_t *other, item_id_t id, int32_t max, int32_t quantity) { +static inline bool G_AddAmmoAndCap(gentity_t* other, item_id_t id, int32_t max, int32_t quantity) { item_id_t new_id = AmmoConvertId(id); - + if (other->client->pers.inventory[new_id] == AMMO_INFINITE) return false; @@ -2010,7 +2032,8 @@ static inline bool G_AddAmmoAndCap(gentity_t *other, item_id_t id, int32_t max, if (quantity == AMMO_INFINITE) { other->client->pers.inventory[new_id] = AMMO_INFINITE; - } else { + } + else { other->client->pers.inventory[new_id] += quantity; if (other->client->pers.inventory[new_id] > max) other->client->pers.inventory[new_id] = max; @@ -2020,16 +2043,16 @@ static inline bool G_AddAmmoAndCap(gentity_t *other, item_id_t id, int32_t max, return true; } -static inline bool G_AddAmmoAndCapQuantity(gentity_t *other, ammo_t ammo) { - gitem_t *item = GetItemByAmmo(ammo); +static inline bool G_AddAmmoAndCapQuantity(gentity_t* other, ammo_t ammo) { + gitem_t* item = GetItemByAmmo(ammo); return G_AddAmmoAndCap(other, item->id, other->client->pers.max_ammo[ammo], item->quantity); } -static inline void G_AdjustAmmoCap(gentity_t *other, ammo_t ammo, int16_t new_max) { +static inline void G_AdjustAmmoCap(gentity_t* other, ammo_t ammo, int16_t new_max) { other->client->pers.max_ammo[ammo] = max(other->client->pers.max_ammo[ammo], new_max); } -static bool Pickup_Bandolier(gentity_t *ent, gentity_t *other) { +static bool Pickup_Bandolier(gentity_t* ent, gentity_t* other) { G_AdjustAmmoCap(other, AMMO_BULLETS, 250); G_AdjustAmmoCap(other, AMMO_SHELLS, 150); G_AdjustAmmoCap(other, AMMO_CELLS, 250); @@ -2047,8 +2070,8 @@ static bool Pickup_Bandolier(gentity_t *ent, gentity_t *other) { return true; } -void G_CheckAutoSwitch(gentity_t *ent, gitem_t *item, bool is_new); -static bool Pickup_Pack(gentity_t *ent, gentity_t *other) { +void G_CheckAutoSwitch(gentity_t* ent, gitem_t* item, bool is_new); +static bool Pickup_Pack(gentity_t* ent, gentity_t* other) { G_AdjustAmmoCap(other, AMMO_BULLETS, 300); G_AdjustAmmoCap(other, AMMO_SHELLS, 200); G_AdjustAmmoCap(other, AMMO_ROCKETS, 100); @@ -2068,8 +2091,8 @@ static bool Pickup_Pack(gentity_t *ent, gentity_t *other) { G_AddAmmoAndCapQuantity(other, AMMO_MAGSLUG); G_AddAmmoAndCapQuantity(other, AMMO_FLECHETTES); G_AddAmmoAndCapQuantity(other, AMMO_DISRUPTOR); - - gitem_t *it = GetItemByIndex(IT_AMMO_GRENADES); + + gitem_t* it = GetItemByIndex(IT_AMMO_GRENADES); if (it) G_CheckAutoSwitch(other, it, !other->client->pers.inventory[IT_AMMO_GRENADES]); @@ -2080,12 +2103,12 @@ static bool Pickup_Pack(gentity_t *ent, gentity_t *other) { //====================================================================== -static void Use_Powerup_BroadcastMsg(gentity_t *ent, gitem_t *item, const char *sound_name, const char *announcer_name) { +static void Use_Powerup_BroadcastMsg(gentity_t* ent, gitem_t* item, const char* sound_name, const char* announcer_name) { if (deathmatch->integer) { if (g_quadhog->integer && item->id == IT_POWERUP_QUAD) { gi.LocBroadcast_Print(PRINT_CENTER, "{} is the Quad Hog!\n", ent->client->resp.netname); - //} else { - // gi.LocBroadcast_Print(PRINT_HIGH, "{} got the {}!\n", ent->client->resp.netname, item->pickup_name); + //} else { + // gi.LocBroadcast_Print(PRINT_HIGH, "{} got the {}!\n", ent->client->resp.netname, item->pickup_name); } if (RS(RS_MM) || RS(RS_Q3A)) { gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex(sound_name), 1, ATTN_NONE, 0); @@ -2094,7 +2117,7 @@ static void Use_Powerup_BroadcastMsg(gentity_t *ent, gitem_t *item, const char * } } -void Use_Quad(gentity_t *ent, gitem_t *item) { +void Use_Quad(gentity_t* ent, gitem_t* item) { gtime_t timeout; ent->client->pers.inventory[item->id]--; @@ -2102,7 +2125,8 @@ void Use_Quad(gentity_t *ent, gitem_t *item) { if (quad_drop_timeout_hack) { timeout = quad_drop_timeout_hack; quad_drop_timeout_hack = 0_ms; - } else { + } + else { timeout = 30_sec; } @@ -2112,7 +2136,7 @@ void Use_Quad(gentity_t *ent, gitem_t *item) { } // ===================================================================== -void Use_Haste(gentity_t *ent, gitem_t *item) { +void Use_Haste(gentity_t* ent, gitem_t* item) { gtime_t timeout; ent->client->pers.inventory[item->id]--; @@ -2120,7 +2144,8 @@ void Use_Haste(gentity_t *ent, gitem_t *item) { if (haste_drop_timeout_hack) { timeout = haste_drop_timeout_hack; haste_drop_timeout_hack = 0_ms; - } else { + } + else { timeout = 30_sec; } @@ -2131,7 +2156,7 @@ void Use_Haste(gentity_t *ent, gitem_t *item) { //====================================================================== -static void Use_Double(gentity_t *ent, gitem_t *item) { +static void Use_Double(gentity_t* ent, gitem_t* item) { gtime_t timeout; ent->client->pers.inventory[item->id]--; @@ -2139,7 +2164,8 @@ static void Use_Double(gentity_t *ent, gitem_t *item) { if (double_drop_timeout_hack) { timeout = double_drop_timeout_hack; double_drop_timeout_hack = 0_ms; - } else { + } + else { timeout = 30_sec; } @@ -2150,21 +2176,21 @@ static void Use_Double(gentity_t *ent, gitem_t *item) { //====================================================================== -static void Use_Breather(gentity_t *ent, gitem_t *item) { +static void Use_Breather(gentity_t* ent, gitem_t* item) { ent->client->pers.inventory[item->id]--; ent->client->pu_time_rebreather = max(level.time, ent->client->pu_time_rebreather) + (RS(RS_MM) ? 45_sec : 30_sec); } //====================================================================== -static void Use_Envirosuit(gentity_t *ent, gitem_t *item) { +static void Use_Envirosuit(gentity_t* ent, gitem_t* item) { ent->client->pers.inventory[item->id]--; ent->client->pu_time_enviro = max(level.time, ent->client->pu_time_enviro) + 30_sec; } //====================================================================== -static void Use_Protection(gentity_t *ent, gitem_t *item) { +static void Use_Protection(gentity_t* ent, gitem_t* item) { gtime_t timeout; ent->client->pers.inventory[item->id]--; @@ -2172,7 +2198,8 @@ static void Use_Protection(gentity_t *ent, gitem_t *item) { if (protection_drop_timeout_hack) { timeout = protection_drop_timeout_hack; protection_drop_timeout_hack = 0_ms; - } else { + } + else { timeout = 30_sec; } @@ -2183,9 +2210,9 @@ static void Use_Protection(gentity_t *ent, gitem_t *item) { //====================================================================== -void Powerup_ApplyRegeneration(gentity_t *ent) { +void Powerup_ApplyRegeneration(gentity_t* ent) { bool noise = false; - gclient_t *cl; + gclient_t* cl; float volume = 1.0; bool mod = g_instagib->integer || g_nadefest->integer; bool no_health = mod || GTF(GTF_ARENA) || g_no_health->integer; @@ -2227,7 +2254,7 @@ void Powerup_ApplyRegeneration(gentity_t *ent) { } } -static void Use_Regeneration(gentity_t *ent, gitem_t *item) { +static void Use_Regeneration(gentity_t* ent, gitem_t* item) { gtime_t timeout; ent->client->pers.inventory[item->id]--; @@ -2235,7 +2262,8 @@ static void Use_Regeneration(gentity_t *ent, gitem_t *item) { if (regeneration_drop_timeout_hack) { timeout = regeneration_drop_timeout_hack; regeneration_drop_timeout_hack = 0_ms; - } else { + } + else { timeout = 30_sec; } @@ -2244,7 +2272,7 @@ static void Use_Regeneration(gentity_t *ent, gitem_t *item) { Use_Powerup_BroadcastMsg(ent, item, "items/protect.wav", "regeneration"); } -static void Use_Invisibility(gentity_t *ent, gitem_t *item) { +static void Use_Invisibility(gentity_t* ent, gitem_t* item) { gtime_t timeout; ent->client->pers.inventory[item->id]--; @@ -2252,7 +2280,8 @@ static void Use_Invisibility(gentity_t *ent, gitem_t *item) { if (invisibility_drop_timeout_hack) { timeout = invisibility_drop_timeout_hack; invisibility_drop_timeout_hack = 0_ms; - } else { + } + else { timeout = 30_sec; } @@ -2263,21 +2292,22 @@ static void Use_Invisibility(gentity_t *ent, gitem_t *item) { //====================================================================== -static void Use_Silencer(gentity_t *ent, gitem_t *item) { +static void Use_Silencer(gentity_t* ent, gitem_t* item) { ent->client->pers.inventory[item->id]--; ent->client->silencer_shots += 30; } //====================================================================== -static bool Pickup_Key(gentity_t *ent, gentity_t *other) { +static bool Pickup_Key(gentity_t* ent, gentity_t* other) { if (coop->integer) { if (ent->item->id == IT_KEY_POWER_CUBE || ent->item->id == IT_KEY_EXPLOSIVE_CHARGES) { if (other->client->pers.power_cubes & ((ent->spawnflags & SPAWNFLAG_EDITOR_MASK).value >> 8)) return false; other->client->pers.inventory[ent->item->id]++; other->client->pers.power_cubes |= ((ent->spawnflags & SPAWNFLAG_EDITOR_MASK).value >> 8); - } else { + } + else { if (other->client->pers.inventory[ent->item->id]) return false; other->client->pers.inventory[ent->item->id] = 1; @@ -2292,7 +2322,7 @@ static bool Pickup_Key(gentity_t *ent, gentity_t *other) { //====================================================================== -bool Add_Ammo(gentity_t *ent, gitem_t *item, int count) { +bool Add_Ammo(gentity_t* ent, gitem_t* item, int count) { if (!ent->client || item->tag < AMMO_BULLETS || item->tag >= AMMO_MAX) return false; @@ -2300,7 +2330,7 @@ bool Add_Ammo(gentity_t *ent, gitem_t *item, int count) { } // we just got weapon `item`, check if we should switch to it -void G_CheckAutoSwitch(gentity_t *ent, gitem_t *item, bool is_new) { +void G_CheckAutoSwitch(gentity_t* ent, gitem_t* item, bool is_new) { // already using or switching to if (ent->client->pers.weapon == item || ent->client->newweapon == item) @@ -2336,13 +2366,14 @@ void G_CheckAutoSwitch(gentity_t *ent, gitem_t *item, bool is_new) { break; case IT_WEAPON_SHOTGUN: // switch only to SSG - if (item->id != IT_WEAPON_SSHOTGUN) + if (item->id != IT_WEAPON_SSHOTGUN) return; break; case IT_WEAPON_MACHINEGUN: if (RS(RS_Q3A)) { // always switch from mg in Q3A - } else { + } + else { // switch only to CG if (item->id != IT_WEAPON_CHAINGUN) return; @@ -2363,7 +2394,7 @@ void G_CheckAutoSwitch(gentity_t *ent, gitem_t *item, bool is_new) { ent->client->newweapon = item; } -static bool Pickup_Ammo(gentity_t *ent, gentity_t *other) { +static bool Pickup_Ammo(gentity_t* ent, gentity_t* other) { bool weapon = !!(ent->item->flags & IF_WEAPON); int count, oldcount; @@ -2391,7 +2422,7 @@ static bool Pickup_Ammo(gentity_t *ent, gentity_t *other) { return true; } -static void Drop_Ammo(gentity_t *ent, gitem_t *item) { +static void Drop_Ammo(gentity_t* ent, gitem_t* item) { // [Paril-KEX] if (InfiniteAmmoOn(item)) return; @@ -2401,7 +2432,7 @@ static void Drop_Ammo(gentity_t *ent, gitem_t *item) { if (ent->client->pers.inventory[index] <= 0) return; - gentity_t *drop = Drop_Item(ent, item); + gentity_t* drop = Drop_Item(ent, item); drop->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER; drop->svflags &= ~SVF_INSTANCED; @@ -2429,7 +2460,7 @@ static void Drop_Ammo(gentity_t *ent, gitem_t *item) { //====================================================================== -static THINK(MegaHealth_think) (gentity_t *self) -> void { +static THINK(MegaHealth_think) (gentity_t* self) -> void { int32_t health = self->max_health; if (health < self->owner->max_health) health = self->owner->max_health; @@ -2447,7 +2478,7 @@ static THINK(MegaHealth_think) (gentity_t *self) -> void { G_FreeEntity(self); } -static bool Pickup_Health(gentity_t *ent, gentity_t *other) { +static bool Pickup_Health(gentity_t* ent, gentity_t* other) { int health_flags = (ent->style ? ent->style : ent->item->tag); if (!(health_flags & HEALTH_IGNORE_MAX)) @@ -2493,7 +2524,8 @@ static bool Pickup_Health(gentity_t *ent, gentity_t *other) { // mega health doesn't need to be special in SP // since it never respawns. other->client->pers.megahealth_time = 5_sec; - } else { + } + else { ent->think = MegaHealth_think; ent->nextthink = level.time + 5_sec; ent->owner = other; @@ -2506,7 +2538,8 @@ static bool Pickup_Health(gentity_t *ent, gentity_t *other) { ent->max_health = ent->owner->health - count; } - } else { + } + else { SetRespawn(ent, RS(RS_Q3A) ? 60_sec : 30_sec); } @@ -2515,7 +2548,7 @@ static bool Pickup_Health(gentity_t *ent, gentity_t *other) { //====================================================================== -item_id_t ArmorIndex(gentity_t *ent) { +item_id_t ArmorIndex(gentity_t* ent) { if (ent->svflags & SVF_MONSTER) return ent->monsterinfo.armor_type; @@ -2524,8 +2557,9 @@ item_id_t ArmorIndex(gentity_t *ent) { if (ent->client->pers.inventory[IT_ARMOR_JACKET] > 0 || ent->client->pers.inventory[IT_ARMOR_COMBAT] > 0 || ent->client->pers.inventory[IT_ARMOR_BODY] > 0) - return IT_ARMOR_COMBAT; - } else { + return IT_ARMOR_COMBAT; + } + else { if (ent->client->pers.inventory[IT_ARMOR_JACKET] > 0) return IT_ARMOR_JACKET; else if (ent->client->pers.inventory[IT_ARMOR_COMBAT] > 0) @@ -2538,7 +2572,7 @@ item_id_t ArmorIndex(gentity_t *ent) { return IT_NULL; } -static bool Pickup_Armor_Q3(gentity_t *ent, gentity_t *other, int32_t base_count) { +static bool Pickup_Armor_Q3(gentity_t* ent, gentity_t* other, int32_t base_count) { if (other->client->pers.inventory[IT_ARMOR_COMBAT] >= other->client->pers.max_health * 2) return false; @@ -2558,10 +2592,10 @@ static bool Pickup_Armor_Q3(gentity_t *ent, gentity_t *other, int32_t base_count return true; } -static bool Pickup_Armor(gentity_t *ent, gentity_t *other) { +static bool Pickup_Armor(gentity_t* ent, gentity_t* other) { item_id_t old_armor_index; - const gitem_armor_t *oldinfo; - const gitem_armor_t *newinfo; + const gitem_armor_t* oldinfo; + const gitem_armor_t* newinfo; int newcount; float salvage; int salvagecount; @@ -2612,7 +2646,8 @@ static bool Pickup_Armor(gentity_t *ent, gentity_t *other) { // change armor to new item with computed value other->client->pers.inventory[ent->item->id] = newcount; - } else { + } + else { // calc new armor values salvage = newinfo->normal_protection / oldinfo->normal_protection; salvagecount = (int)(salvage * base_count); @@ -2639,7 +2674,7 @@ static bool Pickup_Armor(gentity_t *ent, gentity_t *other) { //====================================================================== -item_id_t PowerArmorType(gentity_t *ent) { +item_id_t PowerArmorType(gentity_t* ent) { if (!ent->client) return IT_NULL; @@ -2655,11 +2690,12 @@ item_id_t PowerArmorType(gentity_t *ent) { return IT_NULL; } -static void Use_PowerArmor(gentity_t *ent, gitem_t *item) { +static void Use_PowerArmor(gentity_t* ent, gitem_t* item) { if (ent->flags & FL_POWER_ARMOR) { ent->flags &= ~(FL_POWER_ARMOR | FL_WANTS_POWER_ARMOR); gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); - } else { + } + else { if (!ent->client->pers.inventory[IT_AMMO_CELLS]) { gi.LocClient_Print(ent, PRINT_HIGH, "$g_no_cells_power_armor"); return; @@ -2675,7 +2711,7 @@ static void Use_PowerArmor(gentity_t *ent, gitem_t *item) { } } -static bool Pickup_PowerArmor(gentity_t *ent, gentity_t *other) { +static bool Pickup_PowerArmor(gentity_t* ent, gentity_t* other) { other->client->pers.inventory[ent->item->id]++; SetRespawn(ent, gtime_t::from_sec(ent->item->quantity)); @@ -2686,7 +2722,7 @@ static bool Pickup_PowerArmor(gentity_t *ent, gentity_t *other) { return true; } -static void Drop_PowerArmor(gentity_t *ent, gitem_t *item) { +static void Drop_PowerArmor(gentity_t* ent, gitem_t* item) { if ((ent->flags & FL_POWER_ARMOR) && (ent->client->pers.inventory[item->id] == 1)) Use_PowerArmor(ent, item); Drop_General(ent, item); @@ -2694,7 +2730,7 @@ static void Drop_PowerArmor(gentity_t *ent, gitem_t *item) { //====================================================================== -bool Entity_IsVisibleToPlayer(gentity_t *ent, gentity_t *player) { +bool Entity_IsVisibleToPlayer(gentity_t* ent, gentity_t* player) { // Q2Eaks make eyecam chase target invisible, but keep other client visible if (g_eyecam->integer && player->client->follow_target && ent == player->client->follow_target) return false; @@ -2709,7 +2745,7 @@ bool Entity_IsVisibleToPlayer(gentity_t *ent, gentity_t *player) { Touch_Item =============== */ -TOUCH(Touch_Item) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool other_touching_self) -> void { +TOUCH(Touch_Item) (gentity_t* ent, gentity_t* other, const trace_t& tr, bool other_touching_self) -> void { bool taken; if (!other->client) @@ -2721,7 +2757,7 @@ TOUCH(Touch_Item) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool oth if (!ent->item->pickup) return; // not a grabbable item? - gitem_t *it = ent->item; + gitem_t* it = ent->item; // already got this instanced item if (coop->integer && P_UseCoopInstancedItems()) { @@ -2776,7 +2812,7 @@ TOUCH(Touch_Item) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool oth case IT_ARMOR_BODY: case IT_POWER_SCREEN: case IT_POWER_SHIELD: - case IT_ADRENALINE: + case IT_ADRENALINE: case IT_HEALTH_MEGA: case IT_POWERUP_QUAD: case IT_POWERUP_DOUBLE: @@ -2821,7 +2857,7 @@ TOUCH(Touch_Item) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool oth // [Paril-KEX] see above msg; this also disables the message in DM // since there's no need to print pickup messages in DM (this wasn't // even a documented feature, relays were traditionally used for this) - const char *message_backup = nullptr; + const char* message_backup = nullptr; if (deathmatch->integer || (coop->integer && P_UseCoopInstancedItems())) std::swap(message_backup, ent->message); @@ -2846,7 +2882,8 @@ TOUCH(Touch_Item) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool oth // if not dropped else should_remove = ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER) || !(it->flags & IF_STAY_COOP); - } else + } + else should_remove = !deathmatch->integer || ent->spawnflags.has(SPAWNFLAG_ITEM_DROPPED | SPAWNFLAG_ITEM_DROPPED_PLAYER); if (should_remove) { @@ -2860,14 +2897,14 @@ TOUCH(Touch_Item) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool oth //====================================================================== -static TOUCH(drop_temp_touch) (gentity_t *ent, gentity_t *other, const trace_t &tr, bool other_touching_self) -> void { +static TOUCH(drop_temp_touch) (gentity_t* ent, gentity_t* other, const trace_t& tr, bool other_touching_self) -> void { if (other == ent->owner) return; Touch_Item(ent, other, tr, other_touching_self); } -static THINK(drop_make_touchable) (gentity_t *ent) -> void { +static THINK(drop_make_touchable) (gentity_t* ent) -> void { ent->touch = Touch_Item; if (deathmatch->integer) { ent->nextthink = level.time + 29_sec; @@ -2875,8 +2912,8 @@ static THINK(drop_make_touchable) (gentity_t *ent) -> void { } } -gentity_t *Drop_Item(gentity_t *ent, gitem_t *item) { - gentity_t *dropped; +gentity_t* Drop_Item(gentity_t* ent, gitem_t* item) { + gentity_t* dropped; vec3_t forward, right; vec3_t offset; @@ -2903,7 +2940,8 @@ gentity_t *Drop_Item(gentity_t *ent, gitem_t *item) { dropped->s.origin = G_ProjectSource(ent->s.origin, offset, forward, right); trace = gi.trace(ent->s.origin, dropped->mins, dropped->maxs, dropped->s.origin, ent, CONTENTS_SOLID); dropped->s.origin = trace.endpos; - } else { + } + else { AngleVectors(ent->s.angles, forward, right, nullptr); dropped->s.origin = (ent->absmin + ent->absmax) / 2; } @@ -2923,14 +2961,15 @@ gentity_t *Drop_Item(gentity_t *ent, gitem_t *item) { return dropped; } -static USE(Use_Item) (gentity_t *ent, gentity_t *other, gentity_t *activator) -> void { +static USE(Use_Item) (gentity_t* ent, gentity_t* other, gentity_t* activator) -> void { ent->svflags &= ~SVF_NOCLIENT; ent->use = nullptr; if (ent->spawnflags.has(SPAWNFLAG_ITEM_NO_TOUCH)) { ent->solid = SOLID_BBOX; ent->touch = nullptr; - } else { + } + else { ent->solid = SOLID_TRIGGER; ent->touch = Touch_Item; } @@ -2947,12 +2986,13 @@ FinishSpawningItem previously 'droptofloor' ================ */ -static THINK(FinishSpawningItem) (gentity_t *ent) -> void { +static THINK(FinishSpawningItem) (gentity_t* ent) -> void { // [Paril-KEX] scale foodcube based on how much we ingested if (strcmp(ent->classname, "item_foodcube") == 0) { - ent->mins = vec3_t{ -8, -8, -8 } * ent->s.scale; - ent->maxs = vec3_t{ 8, 8, 8 } * ent->s.scale; - } else { + ent->mins = vec3_t{ -8, -8, -8 } *ent->s.scale; + ent->maxs = vec3_t{ 8, 8, 8 } *ent->s.scale; + } + else { ent->mins = { -15, -15, -15 }; ent->maxs = { 15, 15, 15 }; } @@ -2964,12 +3004,13 @@ static THINK(FinishSpawningItem) (gentity_t *ent) -> void { if (ent->spawnflags.has(SPAWNFLAG_ITEM_SUSPENDED)) { ent->movetype = MOVETYPE_NONE; - } else { + } + else { ent->movetype = MOVETYPE_TOSS; vec3_t dest = ent->s.origin + vec3_t{ 0, 0, -4096 }; trace_t tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID); - + if (tr.startsolid) { if (G_FixStuckObject(ent, ent->s.origin) == stuck_result_t::NO_GOOD_POSITION) { if (strcmp(ent->classname, "item_foodcube") == 0) @@ -2980,7 +3021,8 @@ static THINK(FinishSpawningItem) (gentity_t *ent) -> void { return; } } - } else + } + else ent->s.origin = tr.endpos; } @@ -2991,12 +3033,13 @@ static THINK(FinishSpawningItem) (gentity_t *ent) -> void { ent->svflags |= SVF_NOCLIENT; ent->solid = SOLID_NOT; - + if (ent == ent->teammaster) { ent->nextthink = level.time + 10_hz; //if (!ent->think) - ent->think = RespawnItem; - } else + ent->think = RespawnItem; + } + else ent->nextthink = 0_sec; } @@ -3040,11 +3083,11 @@ This will be called for each item spawned in a level, and for each item in each client's inventory. =============== */ -void PrecacheItem(gitem_t *it) { - const char *s, *start; +void PrecacheItem(gitem_t* it) { + const char* s, * start; char data[MAX_QPATH]; ptrdiff_t len; - gitem_t *ammo; + gitem_t* ammo; if (!it) return; @@ -3057,7 +3100,7 @@ void PrecacheItem(gitem_t *it) { gi.modelindex(it->view_model); if (it->icon) gi.imageindex(it->icon); - + // parse everything for its ammo if (it->ammo) { ammo = GetItemByIndex(it->ammo); @@ -3100,8 +3143,8 @@ void PrecacheItem(gitem_t *it) { CheckItemEnabled ============ */ -bool CheckItemEnabled(gitem_t *item) { - cvar_t *cv; +bool CheckItemEnabled(gitem_t* item) { + cvar_t* cv; if (!deathmatch->integer) { if (item->pickup == Pickup_Doppelganger || item->pickup == Pickup_Nuke) @@ -3134,19 +3177,24 @@ bool CheckItemEnabled(gitem_t *item) { if (game.item_inhibit_pu && item->flags & (IF_POWERUP | IF_SPHERE)) { add = game.item_inhibit_pu > 0 ? true : false; subtract = game.item_inhibit_pu < 0 ? true : false; - } else if (game.item_inhibit_pa && item->flags & IF_POWER_ARMOR) { + } + else if (game.item_inhibit_pa && item->flags & IF_POWER_ARMOR) { add = game.item_inhibit_pa > 0 ? true : false; subtract = game.item_inhibit_pa < 0 ? true : false; - } else if (game.item_inhibit_ht && item->flags & IF_HEALTH) { + } + else if (game.item_inhibit_ht && item->flags & IF_HEALTH) { add = game.item_inhibit_ht > 0 ? true : false; subtract = game.item_inhibit_ht < 0 ? true : false; - } else if (game.item_inhibit_ar && item->flags & IF_ARMOR) { + } + else if (game.item_inhibit_ar && item->flags & IF_ARMOR) { add = game.item_inhibit_ar > 0 ? true : false; subtract = game.item_inhibit_ar < 0 ? true : false; - } else if (game.item_inhibit_am && item->flags & IF_AMMO) { + } + else if (game.item_inhibit_am && item->flags & IF_AMMO) { add = game.item_inhibit_am > 0 ? true : false; subtract = game.item_inhibit_am < 0 ? true : false; - } else if (game.item_inhibit_wp && item->flags & IF_WEAPON) { + } + else if (game.item_inhibit_wp && item->flags & IF_WEAPON) { add = game.item_inhibit_wp > 0 ? true : false; subtract = game.item_inhibit_wp < 0 ? true : false; } @@ -3199,18 +3247,18 @@ bool CheckItemEnabled(gitem_t *item) { CheckItemReplacements ============ */ -gitem_t *CheckItemReplacements(gitem_t *item) { - cvar_t *cv; +gitem_t* CheckItemReplacements(gitem_t* item) { + cvar_t* cv; cv = gi.cvar(G_Fmt("{}_replace_{}", level.mapname, item->classname).data(), "", CVAR_NOFLAGS); if (*cv->string) { - gitem_t *out = FindItemByClassname(cv->string); + gitem_t* out = FindItemByClassname(cv->string); return out ? out : item; } cv = gi.cvar(G_Fmt("replace_{}", item->classname).data(), "", CVAR_NOFLAGS); if (*cv->string) { - gitem_t *out = FindItemByClassname(cv->string); + gitem_t* out = FindItemByClassname(cv->string); return out ? out : item; } @@ -3234,7 +3282,7 @@ Item_TriggeredSpawn Create the item marked for spawn creation ============ */ -static USE(Item_TriggeredSpawn) (gentity_t *self, gentity_t *other, gentity_t *activator) -> void { +static USE(Item_TriggeredSpawn) (gentity_t* self, gentity_t* other, gentity_t* activator) -> void { self->svflags &= ~SVF_NOCLIENT; self->use = nullptr; @@ -3262,7 +3310,7 @@ SetTriggeredSpawn Sets up an item to spawn in later. ============ */ -static void SetTriggeredSpawn(gentity_t *ent) { +static void SetTriggeredSpawn(gentity_t* ent) { // don't do anything on key_power_cubes. if (ent->item->id == IT_KEY_POWER_CUBE || ent->item->id == IT_KEY_EXPLOSIVE_CHARGES) return; @@ -3284,7 +3332,7 @@ Items can't be immediately dropped to floor, because they might be on an entity that hasn't spawned yet. ============ */ -bool SpawnItem(gentity_t *ent, gitem_t *item) { +bool SpawnItem(gentity_t* ent, gitem_t* item) { // check for item replacements or disablements item = CheckItemReplacements(item); if (!CheckItemEnabled(item)) { @@ -3307,7 +3355,8 @@ bool SpawnItem(gentity_t *ent, gitem_t *item) { ent->s.effects &= ~(EF_ROTATE | EF_BOB); ent->s.renderfx &= ~RF_GLOW; } - } else if (ent->spawnflags.value >= SPAWNFLAG_ITEM_MAX.value) { + } + else if (ent->spawnflags.value >= SPAWNFLAG_ITEM_MAX.value) { ent->spawnflags = SPAWNFLAG_NONE; gi.Com_PrintFmt("{} has invalid spawnflags set\n", *ent); } @@ -3339,7 +3388,7 @@ bool SpawnItem(gentity_t *ent, gitem_t *item) { if (ent->spawnflags.has(SPAWNFLAG_ITEM_SUSPENDED)) ent->s.effects |= (EF_ROTATE | EF_BOB); - + if (ent->spawnflags.has(SPAWNFLAG_ITEM_TRIGGER_SPAWN)) SetTriggeredSpawn(ent); @@ -3356,7 +3405,7 @@ bool SpawnItem(gentity_t *ent, gitem_t *item) { ent->s.effects |= EF_COLOR_SHELL; } } - + if (!g_item_bobbing->integer && !ent->spawnflags.has(SPAWNFLAG_ITEM_SUSPENDED)) ent->s.effects &= ~EF_BOB; @@ -3375,7 +3424,7 @@ bool SpawnItem(gentity_t *ent, gitem_t *item) { return true; } -void P_ToggleFlashlight(gentity_t *ent, bool state) { +void P_ToggleFlashlight(gentity_t* ent, bool state) { if (!!(ent->flags & FL_FLASHLIGHT) == state) return; @@ -3384,14 +3433,14 @@ void P_ToggleFlashlight(gentity_t *ent, bool state) { gi.sound(ent, CHAN_AUTO, gi.soundindex(ent->flags & FL_FLASHLIGHT ? "items/flashlight_on.wav" : "items/flashlight_off.wav"), 1.f, ATTN_STATIC, 0); } -static void Use_Flashlight(gentity_t *ent, gitem_t *inv) { +static void Use_Flashlight(gentity_t* ent, gitem_t* inv) { P_ToggleFlashlight(ent, !(ent->flags & FL_FLASHLIGHT)); } constexpr size_t MAX_TEMP_POI_POINTS = 128; -void Compass_Update(gentity_t *ent, bool first) { - vec3_t *&points = level.poi_points[ent->s.number - 1]; +void Compass_Update(gentity_t* ent, bool first) { + vec3_t*& points = level.poi_points[ent->s.number - 1]; // deleted for some reason if (!points) @@ -3434,7 +3483,7 @@ void Compass_Update(gentity_t *ent, bool first) { ent->client->help_draw_time = level.time + 200_ms; } -static void Use_Compass(gentity_t *ent, gitem_t *inv) { +static void Use_Compass(gentity_t* ent, gitem_t* inv) { if (deathmatch->integer) { Cmd_ReadyUp_f(ent); return; @@ -3450,10 +3499,10 @@ static void Use_Compass(gentity_t *ent, gitem_t *inv) { ent->client->help_poi_location = level.current_poi; ent->client->help_poi_image = level.current_poi_image; - vec3_t *&points = level.poi_points[ent->s.number - 1]; + vec3_t*& points = level.poi_points[ent->s.number - 1]; if (!points) - points = (vec3_t *)gi.TagMalloc(sizeof(vec3_t) * (MAX_TEMP_POI_POINTS + 1), TAG_LEVEL); + points = (vec3_t*)gi.TagMalloc(sizeof(vec3_t) * (MAX_TEMP_POI_POINTS + 1), TAG_LEVEL); PathRequest request; request.start = ent->s.origin; @@ -3504,17 +3553,18 @@ static void Use_Compass(gentity_t *ent, gitem_t *inv) { ent->client->help_draw_time = 0_ms; Compass_Update(ent, true); - } else { + } + else { P_SendLevelPOI(ent); gi.local_sound(ent, CHAN_AUTO, gi.soundindex("misc/help_marker.wav"), 1.f, ATTN_NORM, 0, GetUnicastKey()); } } -static void Use_Ball(gentity_t *ent, gitem_t *item) { +static void Use_Ball(gentity_t* ent, gitem_t* item) { } -static void Drop_Ball(gentity_t *ent, gitem_t *item) { +static void Drop_Ball(gentity_t* ent, gitem_t* item) { } @@ -3556,132 +3606,132 @@ model="models/items/armor/body/tris.md2" /* armor_info */ &bodyarmor_info }, -/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_ARMOR_COMBAT, - /* classname */ "item_armor_combat", - /* pickup */ Pickup_Armor, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/ar1_pkup.wav", - /* world_model */ "models/items/armor/combat/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_combatarmor", - /* use_name */ "Combat Armor", - /* pickup_name */ "$item_combat_armor", - /* pickup_name_definite */ "$item_combat_armor_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_ARMOR, - /* vwep_model */ nullptr, - /* armor_info */ &combatarmor_info - }, - -/*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_ARMOR_JACKET, - /* classname */ "item_armor_jacket", - /* pickup */ Pickup_Armor, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/ar1_pkup.wav", - /* world_model */ "models/items/armor/jacket/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_jacketarmor", - /* use_name */ "Jacket Armor", - /* pickup_name */ "$item_jacket_armor", - /* pickup_name_definite */ "$item_jacket_armor_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_ARMOR, - /* vwep_model */ nullptr, - /* armor_info */ &jacketarmor_info - }, - -/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_ARMOR_SHARD, - /* classname */ "item_armor_shard", - /* pickup */ Pickup_Armor, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/ar2_pkup.wav", - /* world_model */ "models/items/armor/shard/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_armor_shard", - /* use_name */ "Armor Shard", - /* pickup_name */ "$item_armor_shard", - /* pickup_name_definite */ "$item_armor_shard_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_ARMOR - }, - -/*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_POWER_SCREEN, - /* classname */ "item_power_screen", - /* pickup */ Pickup_PowerArmor, - /* use */ Use_PowerArmor, - /* drop */ Drop_PowerArmor, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/ar3_pkup.wav", - /* world_model */ "models/items/armor/screen/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_powerscreen", - /* use_name */ "Power Screen", - /* pickup_name */ "$item_power_screen", - /* pickup_name_definite */ "$item_power_screen_def", - /* quantity */ 60, - /* ammo */ IT_AMMO_CELLS, - /* chain */ IT_NULL, - /* flags */ IF_ARMOR | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_SCREEN, - /* precaches */ "misc/power2.wav misc/power1.wav" - }, - -/*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_POWER_SHIELD, - /* classname */ "item_power_shield", - /* pickup */ Pickup_PowerArmor, - /* use */ Use_PowerArmor, - /* drop */ Drop_PowerArmor, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/ar3_pkup.wav", - /* world_model */ "models/items/armor/shield/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_powershield", - /* use_name */ "Power Shield", - /* pickup_name */ "$item_power_shield", - /* pickup_name_definite */ "$item_power_shield_def", - /* quantity */ 60, - /* ammo */ IT_AMMO_CELLS, - /* chain */ IT_NULL, - /* flags */ IF_ARMOR | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_SHIELD, - /* precaches */ "misc/power2.wav misc/power1.wav" - }, + /*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_ARMOR_COMBAT, + /* classname */ "item_armor_combat", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar1_pkup.wav", + /* world_model */ "models/items/armor/combat/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_combatarmor", + /* use_name */ "Combat Armor", + /* pickup_name */ "$item_combat_armor", + /* pickup_name_definite */ "$item_combat_armor_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR, + /* vwep_model */ nullptr, + /* armor_info */ &combatarmor_info + }, + + /*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_ARMOR_JACKET, + /* classname */ "item_armor_jacket", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar1_pkup.wav", + /* world_model */ "models/items/armor/jacket/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_jacketarmor", + /* use_name */ "Jacket Armor", + /* pickup_name */ "$item_jacket_armor", + /* pickup_name_definite */ "$item_jacket_armor_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR, + /* vwep_model */ nullptr, + /* armor_info */ &jacketarmor_info + }, + + /*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_ARMOR_SHARD, + /* classname */ "item_armor_shard", + /* pickup */ Pickup_Armor, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar2_pkup.wav", + /* world_model */ "models/items/armor/shard/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_armor_shard", + /* use_name */ "Armor Shard", + /* pickup_name */ "$item_armor_shard", + /* pickup_name_definite */ "$item_armor_shard_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR + }, + + /*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_POWER_SCREEN, + /* classname */ "item_power_screen", + /* pickup */ Pickup_PowerArmor, + /* use */ Use_PowerArmor, + /* drop */ Drop_PowerArmor, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar3_pkup.wav", + /* world_model */ "models/items/armor/screen/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_powerscreen", + /* use_name */ "Power Screen", + /* pickup_name */ "$item_power_screen", + /* pickup_name_definite */ "$item_power_screen_def", + /* quantity */ 60, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SCREEN, + /* precaches */ "misc/power2.wav misc/power1.wav" + }, + + /*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_POWER_SHIELD, + /* classname */ "item_power_shield", + /* pickup */ Pickup_PowerArmor, + /* use */ Use_PowerArmor, + /* drop */ Drop_PowerArmor, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/ar3_pkup.wav", + /* world_model */ "models/items/armor/shield/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_powershield", + /* use_name */ "Power Shield", + /* pickup_name */ "$item_power_shield", + /* pickup_name_definite */ "$item_power_shield_def", + /* quantity */ 60, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_NULL, + /* flags */ IF_ARMOR | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SHIELD, + /* precaches */ "misc/power2.wav misc/power1.wav" + }, // // WEAPONS @@ -3714,2466 +3764,2466 @@ model="models/items/armor/body/tris.md2" /* precaches */ "weapons/grapple/grfire.wav weapons/grapple/grpull.wav weapons/grapple/grhang.wav weapons/grapple/grreset.wav weapons/grapple/grhit.wav weapons/grapple/grfly.wav" }, -/* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_WEAPON_BLASTER, - /* classname */ "weapon_blaster", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Blaster, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_blast/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_blast/tris.md2", - /* icon */ "w_blaster", - /* use_name */ "Blaster", - /* pickup_name */ "$item_blaster", - /* pickup_name_definite */ "$item_blaster_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_WEAPON_BLASTER, - /* flags */ IF_WEAPON | IF_STAY_COOP | IF_NOT_RANDOM, - /* vwep_model */ "#w_blaster.md2", - /* armor_info */ nullptr, - /* tag */ 0, - /* precaches */ "weapons/blastf1a.wav misc/lasfly.wav" - }, + /* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_WEAPON_BLASTER, + /* classname */ "weapon_blaster", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Blaster, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_blast/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_blast/tris.md2", + /* icon */ "w_blaster", + /* use_name */ "Blaster", + /* pickup_name */ "$item_blaster", + /* pickup_name_definite */ "$item_blaster_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_WEAPON_BLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP | IF_NOT_RANDOM, + /* vwep_model */ "#w_blaster.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/blastf1a.wav misc/lasfly.wav" + }, + + /*QUAKED weapon_chainfist (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_chainf/tris.md2" + */ + { + /* id */ IT_WEAPON_CHAINFIST, + /* classname */ "weapon_chainfist", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_ChainFist, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_chainf/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_chainf/tris.md2", + /* icon */ "w_chainfist", + /* use_name */ "Chainfist", + /* pickup_name */ "$item_chainfist", + /* pickup_name_definite */ "$item_chainfist_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_WEAPON_BLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP | IF_NO_HASTE, + /* vwep_model */ "#w_chainfist.md2", + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "weapons/sawidle.wav weapons/sawhit.wav weapons/sawslice.wav", + }, + + /*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_shotg/tris.md2" + */ + { + /* id */ IT_WEAPON_SHOTGUN, + /* classname */ "weapon_shotgun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Shotgun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_shotg/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_shotg/tris.md2", + /* icon */ "w_shotgun", + /* use_name */ "Shotgun", + /* pickup_name */ "$item_shotgun", + /* pickup_name_definite */ "$item_shotgun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_SHELLS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_shotgun.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_SHELLS, + /* precaches */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" + }, + + /*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_shotg2/tris.md2" + */ + { + /* id */ IT_WEAPON_SSHOTGUN, + /* classname */ "weapon_supershotgun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_SuperShotgun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_shotg2/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_shotg2/tris.md2", + /* icon */ "w_sshotgun", + /* use_name */ "Super Shotgun", + /* pickup_name */ "$item_super_shotgun", + /* pickup_name_definite */ "$item_super_shotgun_def", + /* quantity */ 2, + /* ammo */ IT_AMMO_SHELLS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_sshotgun.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_SHELLS, + /* precaches */ "weapons/sshotf1b.wav", + /* sort_id */ 0, + /* quantity_warn */ 10 + }, + + /*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_machn/tris.md2" + */ + { + /* id */ IT_WEAPON_MACHINEGUN, + /* classname */ "weapon_machinegun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Machinegun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_machn/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_machn/tris.md2", + /* icon */ "w_machinegun", + /* use_name */ "Machinegun", + /* pickup_name */ "$item_machinegun", + /* pickup_name_definite */ "$item_machinegun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_BULLETS, + /* chain */ IT_WEAPON_MACHINEGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_machinegun.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_BULLETS, + /* precaches */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + + /*QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_etf_rifle/tris.md2" + */ + { + /* id */ IT_WEAPON_ETF_RIFLE, + /* classname */ "weapon_etf_rifle", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_ETF_Rifle, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_etf_rifle/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_etf_rifle/tris.md2", + /* icon */ "w_etf_rifle", + /* use_name */ "ETF Rifle", + /* pickup_name */ "$item_etf_rifle", + /* pickup_name_definite */ "$item_etf_rifle_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_FLECHETTES, + /* chain */ IT_WEAPON_MACHINEGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_etfrifle.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_FLECHETTES, + /* precaches */ "weapons/nail1.wav models/proj/flechette/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + + /*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_chain/tris.md2" + */ + { + /* id */ IT_WEAPON_CHAINGUN, + /* classname */ "weapon_chaingun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Chaingun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_chain/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_chain/tris.md2", + /* icon */ "w_chaingun", + /* use_name */ "Chaingun", + /* pickup_name */ "$item_chaingun", + /* pickup_name_definite */ "$item_chaingun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_BULLETS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_chaingun.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_BULLETS, + /* precaches */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav weapons/chngnd1a.wav", + /* sort_id */ 0, + /* quantity_warn */ 60 + }, + + /*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + */ + { + /* id */ IT_AMMO_GRENADES, + /* classname */ "ammo_grenades", + /* pickup */ Pickup_Ammo, + /* use */ Use_Weapon, + /* drop */ Drop_Ammo, + /* weaponthink */ Weapon_HandGrenade, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/grenades/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ "models/weapons/v_handgr/tris.md2", + /* icon */ "a_grenades", + /* use_name */ "Grenades", + /* pickup_name */ "$item_grenades", + /* pickup_name_definite */ "$item_grenades_def", + /* quantity */ 5, + /* ammo */ IT_AMMO_GRENADES, + /* chain */ IT_AMMO_GRENADES, + /* flags */ IF_AMMO | IF_WEAPON, + /* vwep_model */ "#a_grenades.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_GRENADES, + /* precaches */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/objects/grenade3/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 2 + }, + + /*QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/weapons/g_trap/tris.md2" + */ + { + /* id */ IT_AMMO_TRAP, + /* classname */ "ammo_trap", + /* pickup */ Pickup_Ammo, + /* use */ Use_Weapon, + /* drop */ Drop_Ammo, + /* weaponthink */ Weapon_Trap, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/weapons/g_trap/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_trap/tris.md2", + /* icon */ "a_trap", + /* use_name */ "Trap", + /* pickup_name */ "$item_trap", + /* pickup_name_definite */ "$item_trap_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_TRAP, + /* chain */ IT_AMMO_GRENADES, + /* flags */ IF_AMMO | IF_WEAPON | IF_NO_INFINITE_AMMO, + /* vwep_model */ "#a_trap.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_TRAP, + /* precaches */ "misc/fhit3.wav weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav models/weapons/z_trap/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 1 + }, + + /*QUAKED ammo_tesla (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/ammo/am_tesl/tris.md2" + */ + { + /* id */ IT_AMMO_TESLA, + /* classname */ "ammo_tesla", + /* pickup */ Pickup_Ammo, + /* use */ Use_Weapon, + /* drop */ Drop_Ammo, + /* weaponthink */ Weapon_Tesla, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_tesl/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ "models/weapons/v_tesla/tris.md2", + /* icon */ "a_tesla", + /* use_name */ "Tesla", + /* pickup_name */ "$item_tesla", + /* pickup_name_definite */ "$item_tesla_def", + /* quantity */ 3, + /* ammo */ IT_AMMO_TESLA, + /* chain */ IT_AMMO_GRENADES, + /* flags */ IF_AMMO | IF_WEAPON | IF_NO_INFINITE_AMMO, + /* vwep_model */ "#a_tesla.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_TESLA, + /* precaches */ "weapons/teslaopen.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/weapons/g_tesla/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 1 + }, + + /*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_launch/tris.md2" + */ + { + /* id */ IT_WEAPON_GLAUNCHER, + /* classname */ "weapon_grenadelauncher", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_GrenadeLauncher, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_launch/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_launch/tris.md2", + /* icon */ "w_glauncher", + /* use_name */ "Grenade Launcher", + /* pickup_name */ "$item_grenade_launcher", + /* pickup_name_definite */ "$item_grenade_launcher_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_GRENADES, + /* chain */ IT_WEAPON_GLAUNCHER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_glauncher.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_GRENADES, + /* precaches */ "models/objects/grenade4/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" + }, + + /*QUAKED weapon_proxlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_plaunch/tris.md2" + */ + { + /* id */ IT_WEAPON_PROXLAUNCHER, + /* classname */ "weapon_proxlauncher", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_ProxLauncher, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_plaunch/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_plaunch/tris.md2", + /* icon */ "w_proxlaunch", + /* use_name */ "Prox Launcher", + /* pickup_name */ "$item_prox_launcher", + /* pickup_name_definite */ "$item_prox_launcher_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_PROX, + /* chain */ IT_WEAPON_GLAUNCHER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_plauncher.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_PROX, + /* precaches */ "weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav weapons/proxwarn.wav weapons/proxopen.wav", + }, + + /*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_rocket/tris.md2" + */ + { + /* id */ IT_WEAPON_RLAUNCHER, + /* classname */ "weapon_rocketlauncher", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_RocketLauncher, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_rocket/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_rocket/tris.md2", + /* icon */ "w_rlauncher", + /* use_name */ "Rocket Launcher", + /* pickup_name */ "$item_rocket_launcher", + /* pickup_name_definite */ "$item_rocket_launcher_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_ROCKETS, + /* chain */ IT_NULL, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_rlauncher.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_ROCKETS, + /* precaches */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" + }, + + /*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_hyperb/tris.md2" + */ + { + /* id */ IT_WEAPON_HYPERBLASTER, + /* classname */ "weapon_hyperblaster", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_HyperBlaster, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_hyperb/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_hyperb/tris.md2", + /* icon */ "w_hyperblaster", + /* use_name */ "HyperBlaster", + /* pickup_name */ "$item_hyperblaster", + /* pickup_name_definite */ "$item_hyperblaster_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_HYPERBLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_hyperblaster.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS, + /* precaches */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + + /*QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_boom/tris.md2" + */ + { + /* id */ IT_WEAPON_IONRIPPER, + /* classname */ "weapon_boomer", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_IonRipper, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_boom/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_boomer/tris.md2", + /* icon */ "w_ripper", + /* use_name */ "Ionripper", + /* pickup_name */ "$item_ionripper", + /* pickup_name_definite */ "$item_ionripper_def", + /* quantity */ 2, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_HYPERBLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_ripper.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS, + /* precaches */ "weapons/rippfire.wav models/objects/boomrang/tris.md2 misc/lasfly.wav", + /* sort_id */ 0, + /* quantity_warn */ 30 + }, + + /*QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_beamer/tris.md2" + */ + { + /* id */ IT_WEAPON_PLASMABEAM, + /* classname */ "weapon_plasmabeam", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_PlasmaBeam, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_beamer/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_beamer/tris.md2", + /* icon */ "w_heatbeam", + /* use_name */ "Plasma Beam", + /* pickup_name */ "$item_plasma_beam", + /* pickup_name_definite */ "$item_plasma_beam_def", + /* quantity */ 2, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_HYPERBLASTER, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_plasma.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS, + /* precaches */ "weapons/bfg__l1a.wav weapons/bfg_hum.wav", + /* sort_id */ 0, + /* quantity_warn */ 50 + }, + + /*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_rail/tris.md2" + */ + { + /* id */ IT_WEAPON_RAILGUN, + /* classname */ "weapon_railgun", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Railgun, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_rail/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_rail/tris.md2", + /* icon */ "w_railgun", + /* use_name */ "Railgun", + /* pickup_name */ "$item_railgun", + /* pickup_name_definite */ "$item_railgun_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_SLUGS, + /* chain */ IT_WEAPON_RAILGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_railgun.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_SLUGS, + /* precaches */ "weapons/rg_hum.wav" + }, + + /*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_shotx/tris.md2" + */ + { + /* id */ IT_WEAPON_PHALANX, + /* classname */ "weapon_phalanx", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Phalanx, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_shotx/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_shotx/tris.md2", + /* icon */ "w_phallanx", + /* use_name */ "Phalanx", + /* pickup_name */ "$item_phalanx", + /* pickup_name_definite */ "$item_phalanx_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_MAGSLUG, + /* chain */ IT_WEAPON_RAILGUN, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_phalanx.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_MAGSLUG, + /* precaches */ "weapons/plasshot.wav sprites/s_photon.sp2 weapons/rockfly.wav" + }, + + /*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_bfg/tris.md2" + */ + { + /* id */ IT_WEAPON_BFG, + /* classname */ "weapon_bfg", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_BFG, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_bfg/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_bfg/tris.md2", + /* icon */ "w_bfg", + /* use_name */ "BFG10K", + /* pickup_name */ "$item_bfg10k", + /* pickup_name_definite */ "$item_bfg10k_def", + /* quantity */ 50, + /* ammo */ IT_AMMO_CELLS, + /* chain */ IT_WEAPON_BFG, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_bfg.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS, + /* precaches */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav", + /* sort_id */ 0, + /* quantity_warn */ 50 + }, + + /*QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_dist/tris.md2" + */ + { + /* id */ IT_WEAPON_DISRUPTOR, + /* classname */ "weapon_disintegrator", + /* pickup */ Pickup_Weapon, + /* use */ Use_Weapon, + /* drop */ Drop_Weapon, + /* weaponthink */ Weapon_Disruptor, + /* pickup_sound */ "misc/w_pkup.wav", + /* world_model */ "models/weapons/g_dist/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ "models/weapons/v_dist/tris.md2", + /* icon */ "w_disintegrator", + /* use_name */ "Disruptor", + /* pickup_name */ "$item_disruptor", + /* pickup_name_definite */ "$item_disruptor_def", + /* quantity */ 1, + /* ammo */ IT_AMMO_ROUNDS, + /* chain */ IT_WEAPON_BFG, + /* flags */ IF_WEAPON | IF_STAY_COOP, + /* vwep_model */ "#w_disrupt.md2", + /* armor_info */ nullptr, + /* tag */ AMMO_DISRUPTOR, + /* precaches */ "models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav", + }, + + // + // AMMO ITEMS + // + + /*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/ammo/shells/medium/tris.md2" + */ + { + /* id */ IT_AMMO_SHELLS, + /* classname */ "ammo_shells", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/shells/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_shells", + /* use_name */ "Shells", + /* pickup_name */ "$item_shells", + /* pickup_name_definite */ "$item_shells_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SHELLS + }, + + /*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/ammo/bullets/medium/tris.md2" + */ + { + /* id */ IT_AMMO_BULLETS, + /* classname */ "ammo_bullets", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/bullets/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_bullets", + /* use_name */ "Bullets", + /* pickup_name */ "$item_bullets", + /* pickup_name_definite */ "$item_bullets_def", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_BULLETS + }, + + /*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/ammo/cells/medium/tris.md2" + */ + { + /* id */ IT_AMMO_CELLS, + /* classname */ "ammo_cells", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/cells/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_cells", + /* use_name */ "Cells", + /* pickup_name */ "$item_cells", + /* pickup_name_definite */ "$item_cells_def", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS + }, + + /*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/ammo/rockets/medium/tris.md2" + */ + { + /* id */ IT_AMMO_ROCKETS, + /* classname */ "ammo_rockets", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/rockets/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_rockets", + /* use_name */ "Rockets", + /* pickup_name */ "$item_rockets", + /* pickup_name_definite */ "$item_rockets_def", + /* quantity */ 5, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_ROCKETS + }, + + /*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/ammo/slugs/medium/tris.md2" + */ + { + /* id */ IT_AMMO_SLUGS, + /* classname */ "ammo_slugs", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/items/ammo/slugs/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_slugs", + /* use_name */ "Slugs", + /* pickup_name */ "$item_slugs", + /* pickup_name_definite */ "$item_slugs_def", + /* quantity */ 5, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SLUGS + }, + + /*QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/objects/ammo/tris.md2" + */ + { + /* id */ IT_AMMO_MAGSLUG, + /* classname */ "ammo_magslug", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/objects/ammo/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_mslugs", + /* use_name */ "Mag Slug", + /* pickup_name */ "$item_mag_slug", + /* pickup_name_definite */ "$item_mag_slug_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_MAGSLUG + }, + + /*QUAKED ammo_flechettes (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/ammo/am_flechette/tris.md2" + */ + { + /* id */ IT_AMMO_FLECHETTES, + /* classname */ "ammo_flechettes", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_flechette/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_flechettes", + /* use_name */ "Flechettes", + /* pickup_name */ "$item_flechettes", + /* pickup_name_definite */ "$item_flechettes_def", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_FLECHETTES + }, + + /*QUAKED ammo_prox (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/ammo/am_prox/tris.md2" + */ + { + /* id */ IT_AMMO_PROX, + /* classname */ "ammo_prox", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_prox/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_prox", + /* use_name */ "Prox", + /* pickup_name */ "$item_prox", + /* pickup_name_definite */ "$item_prox_def", + /* quantity */ 5, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_PROX, + /* precaches */ "models/weapons/g_prox/tris.md2 weapons/proxwarn.wav" + }, + + /*QUAKED ammo_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/ammo/g_nuke/tris.md2" + */ + { + /* id */ IT_AMMO_NUKE, + /* classname */ "ammo_nuke", + /* pickup */ Pickup_Nuke, + /* use */ Use_Nuke, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/weapons/g_nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_nuke", + /* use_name */ "A-M Bomb", + /* pickup_name */ "$item_am_bomb", + /* pickup_name_definite */ "$item_am_bomb_def", + /* quantity */ 300, + /* ammo */ IT_AMMO_NUKE, + /* chain */ IT_NULL, + /* flags */ IF_TIMED | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_AM_BOMB, + /* precaches */ "weapons/nukewarn2.wav world/rumble.wav" + }, + + /*QUAKED ammo_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/ammo/am_disr/tris.md2" + */ + { + /* id */ IT_AMMO_ROUNDS, + /* classname */ "ammo_disruptor", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/ammo/am_disr/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_disruptor", + /* use_name */ "Rounds", + /* pickup_name */ "$item_rounds", + /* pickup_name_definite */ "$item_rounds_def", + /* quantity */ 3, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_DISRUPTOR + }, + + // + // POWERUP ITEMS + // + /*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/quaddama/tris.md2" + */ + { + /* id */ IT_POWERUP_QUAD, + /* classname */ "item_quad", + /* pickup */ Pickup_Powerup, + /* use */ Use_Quad, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/quaddama/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_quad", + /* use_name */ "Quad Damage", + /* pickup_name */ "$item_quad_damage", + /* pickup_name_definite */ "$item_quad_damage_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_QUAD, + /* precaches */ "items/damage.wav items/damage2.wav items/damage3.wav ctf/tech2x.wav" + }, + + /*QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/quadfire/tris.md2" + */ + { + /* id */ IT_POWERUP_HASTE, + /* classname */ "item_quadfire", + /* pickup */ Pickup_Powerup, + /* use */ Use_Haste, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/quadfire/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_quadfire", + /* use_name */ "Haste", + /* pickup_name */ "Haste", + /* pickup_name_definite */ "Haste", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_HASTE, + /* precaches */ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav" + }, + + /*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/invulner/tris.md2" + */ + { + /* id */ IT_POWERUP_PROTECTION, + /* classname */ "item_invulnerability", + /* pickup */ Pickup_Powerup, + /* use */ Use_Protection, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/invulner/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_invulnerability", + /* use_name */ "Protection", + /* pickup_name */ "Protection", + /* pickup_name_definite */ "Protection", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_PROTECTION, + /* precaches */ "items/protect.wav items/protect2.wav items/protect4.wav" + }, + + /*QUAKED item_invisibility (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/cloaker/tris.md2" + */ + { + /* id */ IT_POWERUP_INVISIBILITY, + /* classname */ "item_invisibility", + /* pickup */ Pickup_Powerup, + /* use */ Use_Invisibility, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/cloaker/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_cloaker", + /* use_name */ "Invisibility", + /* pickup_name */ "$item_invisibility", + /* pickup_name_definite */ "$item_invisibility_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_INVISIBILITY, + }, + + /*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/silencer/tris.md2" + */ + { + /* id */ IT_POWERUP_SILENCER, + /* classname */ "item_silencer", + /* pickup */ Pickup_TimedItem, + /* use */ Use_Silencer, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/silencer/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_silencer", + /* use_name */ "Silencer", + /* pickup_name */ "$item_silencer", + /* pickup_name_definite */ "$item_silencer_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SILENCER, + }, + + /*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/breather/tris.md2" + */ + { + /* id */ IT_POWERUP_REBREATHER, + /* classname */ "item_breather", + /* pickup */ Pickup_TimedItem, + /* use */ Use_Breather, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/breather/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_rebreather", + /* use_name */ "Rebreather", + /* pickup_name */ "$item_rebreather", + /* pickup_name_definite */ "$item_rebreather_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_TIMED | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_REBREATHER, + /* precaches */ "items/airout.wav" + }, + + /*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/enviro/tris.md2" + */ + { + /* id */ IT_POWERUP_ENVIROSUIT, + /* classname */ "item_enviro", + /* pickup */ Pickup_TimedItem, + /* use */ Use_Envirosuit, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/enviro/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_envirosuit", + /* use_name */ "Environment Suit", + /* pickup_name */ "$item_environment_suit", + /* pickup_name_definite */ "$item_environment_suit_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_TIMED | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_ENVIROSUIT, + /* precaches */ "items/airout.wav" + }, + + /*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Special item that gives +2 to maximum health + model="models/items/c_head/tris.md2" + */ + { + /* id */ IT_ANCIENT_HEAD, + /* classname */ "item_ancient_head", + /* pickup */ Pickup_LegacyHead, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/c_head/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_fixme", + /* use_name */ "Ancient Head", + /* pickup_name */ "$item_ancient_head", + /* pickup_name_definite */ "$item_ancient_head_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH | IF_NOT_RANDOM, + }, + + /*QUAKED item_legacy_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Special item that gives +5 to maximum health. + model="models/items/legacyhead/tris.md2" + */ + { + /* id */ IT_LEGACY_HEAD, + /* classname */ "item_legacy_head", + /* pickup */ Pickup_LegacyHead, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/legacyhead/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_fixme", + /* use_name */ "Ranger's Head", + /* pickup_name */ "Ranger's Head", + /* pickup_name_definite */ "Ranger's Head", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH | IF_NOT_RANDOM, + }, + + /*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Gives +1 to maximum health, +5 in deathmatch. + model="models/items/adrenal/tris.md2" + */ + { + /* id */ IT_ADRENALINE, + /* classname */ "item_adrenaline", + /* pickup */ Pickup_TimedItem, + /* use */ Use_Adrenaline, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/adrenal/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_adrenaline", + /* use_name */ "Adrenaline", + /* pickup_name */ "$item_adrenaline", + /* pickup_name_definite */ "$item_adrenaline_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_ADRENALINE, + /* precache */ "items/n_health.wav" + }, + + /*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/band/tris.md2" + */ + { + /* id */ IT_BANDOLIER, + /* classname */ "item_bandolier", + /* pickup */ Pickup_Bandolier, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/band/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_bandolier", + /* use_name */ "Bandolier", + /* pickup_name */ "$item_bandolier", + /* pickup_name_definite */ "$item_bandolier_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED + }, + + /*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/pack/tris.md2" + */ + { + /* id */ IT_PACK, + /* classname */ "item_pack", + /* pickup */ Pickup_Pack, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/pack/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_pack", + /* use_name */ "Ammo Pack", + /* pickup_name */ "$item_ammo_pack", + /* pickup_name_definite */ "$item_ammo_pack_def", + /* quantity */ 180, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED + }, + + /*QUAKED item_ir_goggles (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Infrared vision. + model="models/items/goggles/tris.md2" + */ + { + /* id */ IT_IR_GOGGLES, + /* classname */ "item_ir_goggles", + /* pickup */ Pickup_TimedItem, + /* use */ Use_IR, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/goggles/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_ir", + /* use_name */ "IR Goggles", + /* pickup_name */ "$item_ir_goggles", + /* pickup_name_definite */ "$item_ir_goggles_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_IR_GOGGLES, + /* precaches */ "misc/ir_start.wav" + }, + + /*QUAKED item_double (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/ddamage/tris.md2" + */ + { + /* id */ IT_POWERUP_DOUBLE, + /* classname */ "item_double", + /* pickup */ Pickup_Powerup, + /* use */ Use_Double, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/ddamage/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_double", + /* use_name */ "Double Damage", + /* pickup_name */ "$item_double_damage", + /* pickup_name_definite */ "$item_double_damage_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_DOUBLE, + /* precaches */ "misc/ddamage1.wav misc/ddamage2.wav misc/ddamage3.wav ctf/tech2x.wav" + }, + + /*QUAKED item_sphere_vengeance (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/vengnce/tris.md2" + */ + { + /* id */ IT_POWERUP_SPHERE_VENGEANCE, + /* classname */ "item_sphere_vengeance", + /* pickup */ Pickup_Sphere, + /* use */ Use_Vengeance, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/vengnce/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_vengeance", + /* use_name */ "vengeance sphere", + /* pickup_name */ "$item_vengeance_sphere", + /* pickup_name_definite */ "$item_vengeance_sphere_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_SPHERE | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SPHERE_VENGEANCE, + /* precaches */ "spheres/v_idle.wav" + }, + + /*QUAKED item_sphere_hunter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/hunter/tris.md2" + */ + { + /* id */ IT_POWERUP_SPHERE_HUNTER, + /* classname */ "item_sphere_hunter", + /* pickup */ Pickup_Sphere, + /* use */ Use_Hunter, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/hunter/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_hunter", + /* use_name */ "hunter sphere", + /* pickup_name */ "$item_hunter_sphere", + /* pickup_name_definite */ "$item_hunter_sphere_def", + /* quantity */ 120, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_SPHERE | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SPHERE_HUNTER, + /* precaches */ "spheres/h_idle.wav spheres/h_active.wav spheres/h_lurk.wav" + }, + + /*QUAKED item_sphere_defender (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/defender/tris.md2" + */ + { + /* id */ IT_POWERUP_SPHERE_DEFENDER, + /* classname */ "item_sphere_defender", + /* pickup */ Pickup_Sphere, + /* use */ Use_Defender, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/defender/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_defender", + /* use_name */ "defender sphere", + /* pickup_name */ "$item_defender_sphere", + /* pickup_name_definite */ "$item_defender_sphere_def", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_SPHERE | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_SPHERE_DEFENDER, + /* precaches */ "models/objects/laser/tris.md2 models/items/shell/tris.md2 spheres/d_idle.wav" + }, + + /*QUAKED item_doppleganger (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/dopple/tris.md2" + */ + { + /* id */ IT_DOPPELGANGER, + /* classname */ "item_doppleganger", + /* pickup */ Pickup_Doppelganger, + /* use */ Use_Doppelganger, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/dopple/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_doppleganger", + /* use_name */ "Doppelganger", + /* pickup_name */ "$item_doppleganger", + /* pickup_name_definite */ "$item_doppleganger_def", + /* quantity */ 90, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_DOPPELGANGER, + /* precaches */ "models/objects/dopplebase/tris.md2 models/items/spawngro3/tris.md2 medic_commander/monsterspawn1.wav models/items/hunter/tris.md2 models/items/vengnce/tris.md2", + /* sort_id */ 0, + /* quantity_warn */ 1, + /* quantity_max */ 1 + }, + + /* Tag Token */ + { + /* id */ IT_TAG_TOKEN, + /* classname */ nullptr, + /* pickup */ nullptr, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/tagtoken/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB | EF_TAGTRAIL, + /* view_model */ nullptr, + /* icon */ "i_tagtoken", + /* use_name */ "Tag Token", + /* pickup_name */ "$item_tag_token", + /* pickup_name_definite */ "$item_tag_token_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED | IF_NOT_GIVEABLE + }, -/*QUAKED weapon_chainfist (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + // + // KEYS + // +/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP +Key for computer centers. -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_chainf/tris.md2" +model="models/items/keys/data_cd/tris.md2" */ { - /* id */ IT_WEAPON_CHAINFIST, - /* classname */ "weapon_chainfist", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_ChainFist, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_chainf/tris.md2", + /* id */ IT_KEY_DATA_CD, + /* classname */ "key_data_cd", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/data_cd/tris.md2", /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_chainf/tris.md2", - /* icon */ "w_chainfist", - /* use_name */ "Chainfist", - /* pickup_name */ "$item_chainfist", - /* pickup_name_definite */ "$item_chainfist_def", + /* view_model */ nullptr, + /* icon */ "k_datacd", + /* use_name */ "Data CD", + /* pickup_name */ "$item_data_cd", + /* pickup_name_definite */ "$item_data_cd_def", /* quantity */ 0, /* ammo */ IT_NULL, - /* chain */ IT_WEAPON_BLASTER, - /* flags */ IF_WEAPON | IF_STAY_COOP | IF_NO_HASTE, - /* vwep_model */ "#w_chainfist.md2", - /* armor_info */ nullptr, - /* tag */ 0, - /* precaches */ "weapons/sawidle.wav weapons/sawhit.wav weapons/sawslice.wav", + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY }, -/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_shotg/tris.md2" -*/ - { - /* id */ IT_WEAPON_SHOTGUN, - /* classname */ "weapon_shotgun", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Shotgun, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_shotg/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_shotg/tris.md2", - /* icon */ "w_shotgun", - /* use_name */ "Shotgun", - /* pickup_name */ "$item_shotgun", - /* pickup_name_definite */ "$item_shotgun_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_SHELLS, - /* chain */ IT_NULL, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_shotgun.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_SHELLS, - /* precaches */ "weapons/shotgf1b.wav weapons/shotgr1b.wav" - }, - -/*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_shotg2/tris.md2" -*/ - { - /* id */ IT_WEAPON_SSHOTGUN, - /* classname */ "weapon_supershotgun", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_SuperShotgun, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_shotg2/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_shotg2/tris.md2", - /* icon */ "w_sshotgun", - /* use_name */ "Super Shotgun", - /* pickup_name */ "$item_super_shotgun", - /* pickup_name_definite */ "$item_super_shotgun_def", - /* quantity */ 2, - /* ammo */ IT_AMMO_SHELLS, - /* chain */ IT_NULL, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_sshotgun.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_SHELLS, - /* precaches */ "weapons/sshotf1b.wav", - /* sort_id */ 0, - /* quantity_warn */ 10 - }, - -/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_machn/tris.md2" -*/ - { - /* id */ IT_WEAPON_MACHINEGUN, - /* classname */ "weapon_machinegun", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Machinegun, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_machn/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_machn/tris.md2", - /* icon */ "w_machinegun", - /* use_name */ "Machinegun", - /* pickup_name */ "$item_machinegun", - /* pickup_name_definite */ "$item_machinegun_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_BULLETS, - /* chain */ IT_WEAPON_MACHINEGUN, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_machinegun.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_BULLETS, - /* precaches */ "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav", - /* sort_id */ 0, - /* quantity_warn */ 30 - }, - -/*QUAKED weapon_etf_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_etf_rifle/tris.md2" -*/ - { - /* id */ IT_WEAPON_ETF_RIFLE, - /* classname */ "weapon_etf_rifle", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_ETF_Rifle, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_etf_rifle/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_etf_rifle/tris.md2", - /* icon */ "w_etf_rifle", - /* use_name */ "ETF Rifle", - /* pickup_name */ "$item_etf_rifle", - /* pickup_name_definite */ "$item_etf_rifle_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_FLECHETTES, - /* chain */ IT_WEAPON_MACHINEGUN, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_etfrifle.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_FLECHETTES, - /* precaches */ "weapons/nail1.wav models/proj/flechette/tris.md2", - /* sort_id */ 0, - /* quantity_warn */ 30 - }, - -/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_chain/tris.md2" -*/ - { - /* id */ IT_WEAPON_CHAINGUN, - /* classname */ "weapon_chaingun", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Chaingun, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_chain/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_chain/tris.md2", - /* icon */ "w_chaingun", - /* use_name */ "Chaingun", - /* pickup_name */ "$item_chaingun", - /* pickup_name_definite */ "$item_chaingun_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_BULLETS, - /* chain */ IT_NULL, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_chaingun.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_BULLETS, - /* precaches */ "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav weapons/chngnd1a.wav", - /* sort_id */ 0, - /* quantity_warn */ 60 - }, - -/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -*/ - { - /* id */ IT_AMMO_GRENADES, - /* classname */ "ammo_grenades", - /* pickup */ Pickup_Ammo, - /* use */ Use_Weapon, - /* drop */ Drop_Ammo, - /* weaponthink */ Weapon_HandGrenade, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/items/ammo/grenades/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ "models/weapons/v_handgr/tris.md2", - /* icon */ "a_grenades", - /* use_name */ "Grenades", - /* pickup_name */ "$item_grenades", - /* pickup_name_definite */ "$item_grenades_def", - /* quantity */ 5, - /* ammo */ IT_AMMO_GRENADES, - /* chain */ IT_AMMO_GRENADES, - /* flags */ IF_AMMO | IF_WEAPON, - /* vwep_model */ "#a_grenades.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_GRENADES, - /* precaches */ "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/objects/grenade3/tris.md2", - /* sort_id */ 0, - /* quantity_warn */ 2 - }, - -/*QUAKED ammo_trap (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/weapons/g_trap/tris.md2" -*/ - { - /* id */ IT_AMMO_TRAP, - /* classname */ "ammo_trap", - /* pickup */ Pickup_Ammo, - /* use */ Use_Weapon, - /* drop */ Drop_Ammo, - /* weaponthink */ Weapon_Trap, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/weapons/g_trap/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_trap/tris.md2", - /* icon */ "a_trap", - /* use_name */ "Trap", - /* pickup_name */ "$item_trap", - /* pickup_name_definite */ "$item_trap_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_TRAP, - /* chain */ IT_AMMO_GRENADES, - /* flags */ IF_AMMO | IF_WEAPON | IF_NO_INFINITE_AMMO, - /* vwep_model */ "#a_trap.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_TRAP, - /* precaches */ "misc/fhit3.wav weapons/trapcock.wav weapons/traploop.wav weapons/trapsuck.wav weapons/trapdown.wav items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav models/weapons/z_trap/tris.md2", - /* sort_id */ 0, - /* quantity_warn */ 1 - }, - -/*QUAKED ammo_tesla (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/ammo/am_tesl/tris.md2" -*/ - { - /* id */ IT_AMMO_TESLA, - /* classname */ "ammo_tesla", - /* pickup */ Pickup_Ammo, - /* use */ Use_Weapon, - /* drop */ Drop_Ammo, - /* weaponthink */ Weapon_Tesla, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/ammo/am_tesl/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ "models/weapons/v_tesla/tris.md2", - /* icon */ "a_tesla", - /* use_name */ "Tesla", - /* pickup_name */ "$item_tesla", - /* pickup_name_definite */ "$item_tesla_def", - /* quantity */ 3, - /* ammo */ IT_AMMO_TESLA, - /* chain */ IT_AMMO_GRENADES, - /* flags */ IF_AMMO | IF_WEAPON | IF_NO_INFINITE_AMMO, - /* vwep_model */ "#a_tesla.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_TESLA, - /* precaches */ "weapons/teslaopen.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav models/weapons/g_tesla/tris.md2", - /* sort_id */ 0, - /* quantity_warn */ 1 - }, - -/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_launch/tris.md2" -*/ - { - /* id */ IT_WEAPON_GLAUNCHER, - /* classname */ "weapon_grenadelauncher", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_GrenadeLauncher, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_launch/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_launch/tris.md2", - /* icon */ "w_glauncher", - /* use_name */ "Grenade Launcher", - /* pickup_name */ "$item_grenade_launcher", - /* pickup_name_definite */ "$item_grenade_launcher_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_GRENADES, - /* chain */ IT_WEAPON_GLAUNCHER, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_glauncher.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_GRENADES, - /* precaches */ "models/objects/grenade4/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav" - }, - -/*QUAKED weapon_proxlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_plaunch/tris.md2" -*/ - { - /* id */ IT_WEAPON_PROXLAUNCHER, - /* classname */ "weapon_proxlauncher", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_ProxLauncher, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_plaunch/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_plaunch/tris.md2", - /* icon */ "w_proxlaunch", - /* use_name */ "Prox Launcher", - /* pickup_name */ "$item_prox_launcher", - /* pickup_name_definite */ "$item_prox_launcher_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_PROX, - /* chain */ IT_WEAPON_GLAUNCHER, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_plauncher.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_PROX, - /* precaches */ "weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav weapons/proxwarn.wav weapons/proxopen.wav", - }, - -/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_rocket/tris.md2" -*/ - { - /* id */ IT_WEAPON_RLAUNCHER, - /* classname */ "weapon_rocketlauncher", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_RocketLauncher, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_rocket/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_rocket/tris.md2", - /* icon */ "w_rlauncher", - /* use_name */ "Rocket Launcher", - /* pickup_name */ "$item_rocket_launcher", - /* pickup_name_definite */ "$item_rocket_launcher_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_ROCKETS, - /* chain */ IT_NULL, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_rlauncher.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_ROCKETS, - /* precaches */ "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2" - }, - -/*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_hyperb/tris.md2" -*/ - { - /* id */ IT_WEAPON_HYPERBLASTER, - /* classname */ "weapon_hyperblaster", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_HyperBlaster, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_hyperb/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_hyperb/tris.md2", - /* icon */ "w_hyperblaster", - /* use_name */ "HyperBlaster", - /* pickup_name */ "$item_hyperblaster", - /* pickup_name_definite */ "$item_hyperblaster_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_CELLS, - /* chain */ IT_WEAPON_HYPERBLASTER, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_hyperblaster.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS, - /* precaches */ "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav", - /* sort_id */ 0, - /* quantity_warn */ 30 - }, - -/*QUAKED weapon_boomer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_boom/tris.md2" -*/ - { - /* id */ IT_WEAPON_IONRIPPER, - /* classname */ "weapon_boomer", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_IonRipper, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_boom/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_boomer/tris.md2", - /* icon */ "w_ripper", - /* use_name */ "Ionripper", - /* pickup_name */ "$item_ionripper", - /* pickup_name_definite */ "$item_ionripper_def", - /* quantity */ 2, - /* ammo */ IT_AMMO_CELLS, - /* chain */ IT_WEAPON_HYPERBLASTER, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_ripper.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS, - /* precaches */ "weapons/rippfire.wav models/objects/boomrang/tris.md2 misc/lasfly.wav", - /* sort_id */ 0, - /* quantity_warn */ 30 - }, - -/*QUAKED weapon_plasmabeam (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_beamer/tris.md2" -*/ - { - /* id */ IT_WEAPON_PLASMABEAM, - /* classname */ "weapon_plasmabeam", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_PlasmaBeam, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_beamer/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_beamer/tris.md2", - /* icon */ "w_heatbeam", - /* use_name */ "Plasma Beam", - /* pickup_name */ "$item_plasma_beam", - /* pickup_name_definite */ "$item_plasma_beam_def", - /* quantity */ 2, - /* ammo */ IT_AMMO_CELLS, - /* chain */ IT_WEAPON_HYPERBLASTER, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_plasma.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS, - /* precaches */ "weapons/bfg__l1a.wav weapons/bfg_hum.wav", - /* sort_id */ 0, - /* quantity_warn */ 50 - }, - -/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_rail/tris.md2" -*/ - { - /* id */ IT_WEAPON_RAILGUN, - /* classname */ "weapon_railgun", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Railgun, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_rail/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_rail/tris.md2", - /* icon */ "w_railgun", - /* use_name */ "Railgun", - /* pickup_name */ "$item_railgun", - /* pickup_name_definite */ "$item_railgun_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_SLUGS, - /* chain */ IT_WEAPON_RAILGUN, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_railgun.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_SLUGS, - /* precaches */ "weapons/rg_hum.wav" - }, - -/*QUAKED weapon_phalanx (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_shotx/tris.md2" -*/ - { - /* id */ IT_WEAPON_PHALANX, - /* classname */ "weapon_phalanx", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Phalanx, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_shotx/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_shotx/tris.md2", - /* icon */ "w_phallanx", - /* use_name */ "Phalanx", - /* pickup_name */ "$item_phalanx", - /* pickup_name_definite */ "$item_phalanx_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_MAGSLUG, - /* chain */ IT_WEAPON_RAILGUN, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_phalanx.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_MAGSLUG, - /* precaches */ "weapons/plasshot.wav sprites/s_photon.sp2 weapons/rockfly.wav" - }, - -/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_bfg/tris.md2" -*/ - { - /* id */ IT_WEAPON_BFG, - /* classname */ "weapon_bfg", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_BFG, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_bfg/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_bfg/tris.md2", - /* icon */ "w_bfg", - /* use_name */ "BFG10K", - /* pickup_name */ "$item_bfg10k", - /* pickup_name_definite */ "$item_bfg10k_def", - /* quantity */ 50, - /* ammo */ IT_AMMO_CELLS, - /* chain */ IT_WEAPON_BFG, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_bfg.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS, - /* precaches */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav", - /* sort_id */ 0, - /* quantity_warn */ 50 - }, - -/*QUAKED weapon_disintegrator (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_dist/tris.md2" -*/ - { - /* id */ IT_WEAPON_DISRUPTOR, - /* classname */ "weapon_disintegrator", - /* pickup */ Pickup_Weapon, - /* use */ Use_Weapon, - /* drop */ Drop_Weapon, - /* weaponthink */ Weapon_Disruptor, - /* pickup_sound */ "misc/w_pkup.wav", - /* world_model */ "models/weapons/g_dist/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ "models/weapons/v_dist/tris.md2", - /* icon */ "w_disintegrator", - /* use_name */ "Disruptor", - /* pickup_name */ "$item_disruptor", - /* pickup_name_definite */ "$item_disruptor_def", - /* quantity */ 1, - /* ammo */ IT_AMMO_ROUNDS, - /* chain */ IT_WEAPON_BFG, - /* flags */ IF_WEAPON | IF_STAY_COOP, - /* vwep_model */ "#w_disrupt.md2", - /* armor_info */ nullptr, - /* tag */ AMMO_DISRUPTOR, - /* precaches */ "models/proj/disintegrator/tris.md2 weapons/disrupt.wav weapons/disint2.wav weapons/disrupthit.wav", - }, - - // - // AMMO ITEMS - // - -/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/ammo/shells/medium/tris.md2" -*/ - { - /* id */ IT_AMMO_SHELLS, - /* classname */ "ammo_shells", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/items/ammo/shells/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_shells", - /* use_name */ "Shells", - /* pickup_name */ "$item_shells", - /* pickup_name_definite */ "$item_shells_def", - /* quantity */ 10, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_SHELLS - }, - -/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/ammo/bullets/medium/tris.md2" -*/ - { - /* id */ IT_AMMO_BULLETS, - /* classname */ "ammo_bullets", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/items/ammo/bullets/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_bullets", - /* use_name */ "Bullets", - /* pickup_name */ "$item_bullets", - /* pickup_name_definite */ "$item_bullets_def", - /* quantity */ 50, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_BULLETS - }, - -/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/ammo/cells/medium/tris.md2" -*/ - { - /* id */ IT_AMMO_CELLS, - /* classname */ "ammo_cells", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/items/ammo/cells/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_cells", - /* use_name */ "Cells", - /* pickup_name */ "$item_cells", - /* pickup_name_definite */ "$item_cells_def", - /* quantity */ 50, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS - }, - -/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/ammo/rockets/medium/tris.md2" -*/ - { - /* id */ IT_AMMO_ROCKETS, - /* classname */ "ammo_rockets", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/items/ammo/rockets/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_rockets", - /* use_name */ "Rockets", - /* pickup_name */ "$item_rockets", - /* pickup_name_definite */ "$item_rockets_def", - /* quantity */ 5, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_ROCKETS - }, - -/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/ammo/slugs/medium/tris.md2" -*/ - { - /* id */ IT_AMMO_SLUGS, - /* classname */ "ammo_slugs", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/items/ammo/slugs/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_slugs", - /* use_name */ "Slugs", - /* pickup_name */ "$item_slugs", - /* pickup_name_definite */ "$item_slugs_def", - /* quantity */ 5, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_SLUGS - }, - -/*QUAKED ammo_magslug (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/objects/ammo/tris.md2" -*/ - { - /* id */ IT_AMMO_MAGSLUG, - /* classname */ "ammo_magslug", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/objects/ammo/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_mslugs", - /* use_name */ "Mag Slug", - /* pickup_name */ "$item_mag_slug", - /* pickup_name_definite */ "$item_mag_slug_def", - /* quantity */ 10, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_MAGSLUG - }, - -/*QUAKED ammo_flechettes (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/ammo/am_flechette/tris.md2" -*/ - { - /* id */ IT_AMMO_FLECHETTES, - /* classname */ "ammo_flechettes", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/ammo/am_flechette/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_flechettes", - /* use_name */ "Flechettes", - /* pickup_name */ "$item_flechettes", - /* pickup_name_definite */ "$item_flechettes_def", - /* quantity */ 50, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_FLECHETTES - }, - -/*QUAKED ammo_prox (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/ammo/am_prox/tris.md2" -*/ - { - /* id */ IT_AMMO_PROX, - /* classname */ "ammo_prox", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/ammo/am_prox/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_prox", - /* use_name */ "Prox", - /* pickup_name */ "$item_prox", - /* pickup_name_definite */ "$item_prox_def", - /* quantity */ 5, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_PROX, - /* precaches */ "models/weapons/g_prox/tris.md2 weapons/proxwarn.wav" - }, - -/*QUAKED ammo_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/ammo/g_nuke/tris.md2" -*/ - { - /* id */ IT_AMMO_NUKE, - /* classname */ "ammo_nuke", - /* pickup */ Pickup_Nuke, - /* use */ Use_Nuke, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/weapons/g_nuke/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_nuke", - /* use_name */ "A-M Bomb", - /* pickup_name */ "$item_am_bomb", - /* pickup_name_definite */ "$item_am_bomb_def", - /* quantity */ 300, - /* ammo */ IT_AMMO_NUKE, - /* chain */ IT_NULL, - /* flags */ IF_TIMED | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_AM_BOMB, - /* precaches */ "weapons/nukewarn2.wav world/rumble.wav" - }, - -/*QUAKED ammo_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/ammo/am_disr/tris.md2" -*/ - { - /* id */ IT_AMMO_ROUNDS, - /* classname */ "ammo_disruptor", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/ammo/am_disr/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_disruptor", - /* use_name */ "Rounds", - /* pickup_name */ "$item_rounds", - /* pickup_name_definite */ "$item_rounds_def", - /* quantity */ 3, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_DISRUPTOR - }, - -// -// POWERUP ITEMS -// -/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/quaddama/tris.md2" -*/ - { - /* id */ IT_POWERUP_QUAD, - /* classname */ "item_quad", - /* pickup */ Pickup_Powerup, - /* use */ Use_Quad, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/quaddama/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_quad", - /* use_name */ "Quad Damage", - /* pickup_name */ "$item_quad_damage", - /* pickup_name_definite */ "$item_quad_damage_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_QUAD, - /* precaches */ "items/damage.wav items/damage2.wav items/damage3.wav ctf/tech2x.wav" - }, - -/*QUAKED item_quadfire (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/quadfire/tris.md2" -*/ - { - /* id */ IT_POWERUP_HASTE, - /* classname */ "item_quadfire", - /* pickup */ Pickup_Powerup, - /* use */ Use_Haste, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/quadfire/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_quadfire", - /* use_name */ "Haste", - /* pickup_name */ "Haste", - /* pickup_name_definite */ "Haste", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_HASTE, - /* precaches */ "items/quadfire1.wav items/quadfire2.wav items/quadfire3.wav" - }, - -/*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/invulner/tris.md2" -*/ - { - /* id */ IT_POWERUP_PROTECTION, - /* classname */ "item_invulnerability", - /* pickup */ Pickup_Powerup, - /* use */ Use_Protection, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/invulner/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_invulnerability", - /* use_name */ "Protection", - /* pickup_name */ "Protection", - /* pickup_name_definite */ "Protection", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_PROTECTION, - /* precaches */ "items/protect.wav items/protect2.wav items/protect4.wav" - }, - -/*QUAKED item_invisibility (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/cloaker/tris.md2" -*/ - { - /* id */ IT_POWERUP_INVISIBILITY, - /* classname */ "item_invisibility", - /* pickup */ Pickup_Powerup, - /* use */ Use_Invisibility, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/cloaker/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_cloaker", - /* use_name */ "Invisibility", - /* pickup_name */ "$item_invisibility", - /* pickup_name_definite */ "$item_invisibility_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_INVISIBILITY, - }, - -/*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/silencer/tris.md2" -*/ - { - /* id */ IT_POWERUP_SILENCER, - /* classname */ "item_silencer", - /* pickup */ Pickup_TimedItem, - /* use */ Use_Silencer, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/silencer/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_silencer", - /* use_name */ "Silencer", - /* pickup_name */ "$item_silencer", - /* pickup_name_definite */ "$item_silencer_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TIMED | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_SILENCER, - }, - -/*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/breather/tris.md2" -*/ - { - /* id */ IT_POWERUP_REBREATHER, - /* classname */ "item_breather", - /* pickup */ Pickup_TimedItem, - /* use */ Use_Breather, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/breather/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_rebreather", - /* use_name */ "Rebreather", - /* pickup_name */ "$item_rebreather", - /* pickup_name_definite */ "$item_rebreather_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_TIMED | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_REBREATHER, - /* precaches */ "items/airout.wav" - }, - -/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/enviro/tris.md2" -*/ - { - /* id */ IT_POWERUP_ENVIROSUIT, - /* classname */ "item_enviro", - /* pickup */ Pickup_TimedItem, - /* use */ Use_Envirosuit, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/enviro/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_envirosuit", - /* use_name */ "Environment Suit", - /* pickup_name */ "$item_environment_suit", - /* pickup_name_definite */ "$item_environment_suit_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_TIMED | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_ENVIROSUIT, - /* precaches */ "items/airout.wav" - }, - -/*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Special item that gives +2 to maximum health -model="models/items/c_head/tris.md2" -*/ - { - /* id */ IT_ANCIENT_HEAD, - /* classname */ "item_ancient_head", - /* pickup */ Pickup_LegacyHead, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/c_head/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_fixme", - /* use_name */ "Ancient Head", - /* pickup_name */ "$item_ancient_head", - /* pickup_name_definite */ "$item_ancient_head_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH | IF_NOT_RANDOM, - }, - -/*QUAKED item_legacy_head (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Special item that gives +5 to maximum health. -model="models/items/legacyhead/tris.md2" -*/ - { - /* id */ IT_LEGACY_HEAD, - /* classname */ "item_legacy_head", - /* pickup */ Pickup_LegacyHead, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/legacyhead/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_fixme", - /* use_name */ "Ranger's Head", - /* pickup_name */ "Ranger's Head", - /* pickup_name_definite */ "Ranger's Head", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH | IF_NOT_RANDOM, - }, - -/*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Gives +1 to maximum health, +5 in deathmatch. -model="models/items/adrenal/tris.md2" -*/ - { - /* id */ IT_ADRENALINE, - /* classname */ "item_adrenaline", - /* pickup */ Pickup_TimedItem, - /* use */ Use_Adrenaline, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/adrenal/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_adrenaline", - /* use_name */ "Adrenaline", - /* pickup_name */ "$item_adrenaline", - /* pickup_name_definite */ "$item_adrenaline_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_ADRENALINE, - /* precache */ "items/n_health.wav" - }, - -/*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/band/tris.md2" -*/ - { - /* id */ IT_BANDOLIER, - /* classname */ "item_bandolier", - /* pickup */ Pickup_Bandolier, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/band/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_bandolier", - /* use_name */ "Bandolier", - /* pickup_name */ "$item_bandolier", - /* pickup_name_definite */ "$item_bandolier_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TIMED - }, - -/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/pack/tris.md2" -*/ - { - /* id */ IT_PACK, - /* classname */ "item_pack", - /* pickup */ Pickup_Pack, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/pack/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_pack", - /* use_name */ "Ammo Pack", - /* pickup_name */ "$item_ammo_pack", - /* pickup_name_definite */ "$item_ammo_pack_def", - /* quantity */ 180, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TIMED - }, - -/*QUAKED item_ir_goggles (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Infrared vision. -model="models/items/goggles/tris.md2" -*/ - { - /* id */ IT_IR_GOGGLES, - /* classname */ "item_ir_goggles", - /* pickup */ Pickup_TimedItem, - /* use */ Use_IR, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/goggles/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_ir", - /* use_name */ "IR Goggles", - /* pickup_name */ "$item_ir_goggles", - /* pickup_name_definite */ "$item_ir_goggles_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TIMED | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_IR_GOGGLES, - /* precaches */ "misc/ir_start.wav" - }, - -/*QUAKED item_double (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/ddamage/tris.md2" -*/ - { - /* id */ IT_POWERUP_DOUBLE, - /* classname */ "item_double", - /* pickup */ Pickup_Powerup, - /* use */ Use_Double, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/ddamage/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_double", - /* use_name */ "Double Damage", - /* pickup_name */ "$item_double_damage", - /* pickup_name_definite */ "$item_double_damage_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_DOUBLE, - /* precaches */ "misc/ddamage1.wav misc/ddamage2.wav misc/ddamage3.wav ctf/tech2x.wav" - }, - -/*QUAKED item_sphere_vengeance (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/vengnce/tris.md2" -*/ - { - /* id */ IT_POWERUP_SPHERE_VENGEANCE, - /* classname */ "item_sphere_vengeance", - /* pickup */ Pickup_Sphere, - /* use */ Use_Vengeance, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/vengnce/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_vengeance", - /* use_name */ "vengeance sphere", - /* pickup_name */ "$item_vengeance_sphere", - /* pickup_name_definite */ "$item_vengeance_sphere_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_SPHERE | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_SPHERE_VENGEANCE, - /* precaches */ "spheres/v_idle.wav" - }, - -/*QUAKED item_sphere_hunter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/hunter/tris.md2" -*/ - { - /* id */ IT_POWERUP_SPHERE_HUNTER, - /* classname */ "item_sphere_hunter", - /* pickup */ Pickup_Sphere, - /* use */ Use_Hunter, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/hunter/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_hunter", - /* use_name */ "hunter sphere", - /* pickup_name */ "$item_hunter_sphere", - /* pickup_name_definite */ "$item_hunter_sphere_def", - /* quantity */ 120, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_SPHERE | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_SPHERE_HUNTER, - /* precaches */ "spheres/h_idle.wav spheres/h_active.wav spheres/h_lurk.wav" - }, - -/*QUAKED item_sphere_defender (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/defender/tris.md2" -*/ - { - /* id */ IT_POWERUP_SPHERE_DEFENDER, - /* classname */ "item_sphere_defender", - /* pickup */ Pickup_Sphere, - /* use */ Use_Defender, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/defender/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_defender", - /* use_name */ "defender sphere", - /* pickup_name */ "$item_defender_sphere", - /* pickup_name_definite */ "$item_defender_sphere_def", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_SPHERE | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_SPHERE_DEFENDER, - /* precaches */ "models/objects/laser/tris.md2 models/items/shell/tris.md2 spheres/d_idle.wav" - }, - -/*QUAKED item_doppleganger (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/dopple/tris.md2" -*/ - { - /* id */ IT_DOPPELGANGER, - /* classname */ "item_doppleganger", - /* pickup */ Pickup_Doppelganger, - /* use */ Use_Doppelganger, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/dopple/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_doppleganger", - /* use_name */ "Doppelganger", - /* pickup_name */ "$item_doppleganger", - /* pickup_name_definite */ "$item_doppleganger_def", - /* quantity */ 90, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, -/* flags */ IF_TIMED | IF_POWERUP_WHEEL, -/* vwep_model */ nullptr, -/* armor_info */ nullptr, -/* tag */ POWERUP_DOPPELGANGER, -/* precaches */ "models/objects/dopplebase/tris.md2 models/items/spawngro3/tris.md2 medic_commander/monsterspawn1.wav models/items/hunter/tris.md2 models/items/vengnce/tris.md2", -/* sort_id */ 0, -/* quantity_warn */ 1, -/* quantity_max */ 1 -}, - -/* Tag Token */ - { - /* id */ IT_TAG_TOKEN, - /* classname */ nullptr, - /* pickup */ nullptr, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/tagtoken/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB | EF_TAGTRAIL, - /* view_model */ nullptr, - /* icon */ "i_tagtoken", - /* use_name */ "Tag Token", - /* pickup_name */ "$item_tag_token", - /* pickup_name_definite */ "$item_tag_token_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TIMED | IF_NOT_GIVEABLE - }, - - // - // KEYS - // -/*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Key for computer centers. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/data_cd/tris.md2" -*/ - { - /* id */ IT_KEY_DATA_CD, - /* classname */ "key_data_cd", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/data_cd/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_datacd", - /* use_name */ "Data CD", - /* pickup_name */ "$item_data_cd", - /* pickup_name_definite */ "$item_data_cd_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Power Cubes for warehouse. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/power/tris.md2" -*/ - { - /* id */ IT_KEY_POWER_CUBE, - /* classname */ "key_power_cube", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/power/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_powercube", - /* use_name */ "Power Cube", - /* pickup_name */ "$item_power_cube", - /* pickup_name_definite */ "$item_power_cube_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_explosive_charges (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Explosive Charges - for N64. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/n64/charge/tris.md2" -*/ - { - /* id */ IT_KEY_EXPLOSIVE_CHARGES, - /* classname */ "key_explosive_charges", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/n64/charge/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "n64/i_charges", - /* use_name */ "Explosive Charges", - /* pickup_name */ "$item_explosive_charges", - /* pickup_name_definite */ "$item_explosive_charges_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_yellow_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Normal door key - Yellow - for N64. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/n64/yellow_key/tris.md2" -*/ - { - /* id */ IT_KEY_YELLOW, - /* classname */ "key_yellow_key", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/n64/yellow_key/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "n64/i_yellow_key", - /* use_name */ "Yellow Key", - /* pickup_name */ "$item_yellow_key", - /* pickup_name_definite */ "$item_yellow_key_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_power_core (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Power Core key - for N64. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/n64/power_core/tris.md2" -*/ - { - /* id */ IT_KEY_POWER_CORE, - /* classname */ "key_power_core", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/n64/power_core/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_pyramid", - /* use_name */ "Power Core", - /* pickup_name */ "$item_power_core", - /* pickup_name_definite */ "$item_power_core_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Key for the entrance of jail3. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/pyramid/tris.md2" -*/ - { - /* id */ IT_KEY_PYRAMID, - /* classname */ "key_pyramid", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/pyramid/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_pyramid", - /* use_name */ "Pyramid Key", - /* pickup_name */ "$item_pyramid_key", - /* pickup_name_definite */ "$item_pyramid_key_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Key for the city computer. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/spinner/tris.md2" -*/ - { - /* id */ IT_KEY_DATA_SPINNER, - /* classname */ "key_data_spinner", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/spinner/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_dataspin", - /* use_name */ "Data Spinner", - /* pickup_name */ "$item_data_spinner", - /* pickup_name_definite */ "$item_data_spinner_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Security pass for the security level. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/pass/tris.md2" -*/ - { - /* id */ IT_KEY_PASS, - /* classname */ "key_pass", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/pass/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_security", - /* use_name */ "Security Pass", - /* pickup_name */ "$item_security_pass", - /* pickup_name_definite */ "$item_security_pass_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Normal door key - Blue. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/key/tris.md2" -*/ - { - /* id */ IT_KEY_BLUE_KEY, - /* classname */ "key_blue_key", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/key/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_bluekey", - /* use_name */ "Blue Key", - /* pickup_name */ "$item_blue_key", - /* pickup_name_definite */ "$item_blue_key_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Normal door key - Red. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/red_key/tris.md2" -*/ - { - /* id */ IT_KEY_RED_KEY, - /* classname */ "key_red_key", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/red_key/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_redkey", - /* use_name */ "Red Key", - /* pickup_name */ "$item_red_key", - /* pickup_name_definite */ "$item_red_key_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Normal door key - Green. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/keys/green_key/tris.md2" -*/ - { - /* id */ IT_KEY_GREEN_KEY, - /* classname */ "key_green_key", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/green_key/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "k_green", - /* use_name */ "Green Key", - /* pickup_name */ "$item_green_key", - /* pickup_name_definite */ "$item_green_key_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Key - Tank Commander's Head. -model="models/monsters/commandr/head/tris.md2" -*/ - { - /* id */ IT_KEY_COMMANDER_HEAD, - /* classname */ "key_commander_head", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/monsters/commandr/head/tris.md2", - /* world_model_flags */ EF_GIB, - /* view_model */ nullptr, - /* icon */ "k_comhead", - /* use_name */ "Commander's Head", - /* pickup_name */ "$item_commanders_head", - /* pickup_name_definite */ "$item_commanders_head_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Key - Airstrike Target for strike. -model="models/items/keys/target/tris.md2" -*/ - { - /* id */ IT_KEY_AIRSTRIKE, - /* classname */ "key_airstrike_target", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/keys/target/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_airstrike", - /* use_name */ "Airstrike Marker", - /* pickup_name */ "$item_airstrike_marker", - /* pickup_name_definite */ "$item_airstrike_marker_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY - }, - -/*QUAKED key_nuke_container (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_nuke/tris.md2" -*/ - { - /* id */ IT_KEY_NUKE_CONTAINER, - /* classname */ "key_nuke_container", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/weapons/g_nuke/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_contain", - /* use_name */ "Antimatter Pod", - /* pickup_name */ "$item_antimatter_pod", - /* pickup_name_definite */ "$item_antimatter_pod_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY, - }, - -/*QUAKED key_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/weapons/g_nuke/tris.md2" -*/ - { - /* id */ IT_KEY_NUKE, - /* classname */ "key_nuke", - /* pickup */ Pickup_Key, - /* use */ nullptr, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/weapons/g_nuke/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_nuke", - /* use_name */ "Antimatter Bomb", - /* pickup_name */ "$item_antimatter_bomb", - /* pickup_name_definite */ "$item_antimatter_bomb_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_KEY, - }, - -/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Health - Stimpack. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/healing/stimpack/tris.md2" -*/ - // Paril: split the healths up so they are always valid classnames - { - /* id */ IT_HEALTH_SMALL, - /* classname */ "item_health_small", - /* pickup */ Pickup_Health, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/s_health.wav", - /* world_model */ "models/items/healing/stimpack/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "i_health", - /* use_name */ "Health", - /* pickup_name */ "$item_stimpack", - /* pickup_name_definite */ "$item_stimpack_def", - /* quantity */ 2, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ HEALTH_IGNORE_MAX - }, - -/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Health - First Aid. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/healing/medium/tris.md2" -*/ - { - /* id */ IT_HEALTH_MEDIUM, - /* classname */ "item_health", - /* pickup */ Pickup_Health, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/n_health.wav", - /* world_model */ "models/items/healing/medium/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "i_health", - /* use_name */ "Health", - /* pickup_name */ "$item_small_medkit", - /* pickup_name_definite */ "$item_small_medkit_def", - /* quantity */ 10, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH - }, - -/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Health - Medkit. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/healing/large/tris.md2" -*/ - { - /* id */ IT_HEALTH_LARGE, - /* classname */ "item_health_large", - /* pickup */ Pickup_Health, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/l_health.wav", - /* world_model */ "models/items/healing/large/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "i_health", - /* use_name */ "Health", - /* pickup_name */ "$item_large_medkit", - /* pickup_name_definite */ "$item_large_medkit", - /* quantity */ 25, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH - }, - -/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Health - Mega Health. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="models/items/mega_h/tris.md2" -*/ - { - /* id */ IT_HEALTH_MEGA, - /* classname */ "item_health_mega", - /* pickup */ Pickup_Health, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/m_health.wav", - /* world_model */ "models/items/mega_h/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "p_megahealth", - /* use_name */ "Mega Health", - /* pickup_name */ "$item_mega_health", - /* pickup_name_definite */ "$item_mega_health_def", - /* quantity */ 100, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ HEALTH_IGNORE_MAX | HEALTH_TIMED - }, - -/*QUAKED item_flag_team_red (1 0.2 0) (-16 -16 -24) (16 16 32) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Red Flag for CTF. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="players/male/flag1.md2" -*/ - { - /* id */ IT_FLAG_RED, - /* classname */ ITEM_CTF_FLAG_RED, - /* pickup */ CTF_PickupFlag, - /* use */ nullptr, - /* drop */ CTF_DropFlag, //Should this be null if we don't want players to drop it manually? - /* weaponthink */ nullptr, - /* pickup_sound */ "ctf/flagtk.wav", - /* world_model */ "players/male/flag1.md2", - /* world_model_flags */ EF_FLAG_RED, - /* view_model */ nullptr, - /* icon */ "i_ctf1", - /* use_name */ "Red Flag", - /* pickup_name */ "$item_red_flag", - /* pickup_name_definite */ "$item_red_flag_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_NONE, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ 0, - /* precaches */ "ctf/flagcap.wav" - }, - -/*QUAKED item_flag_team_blue (1 0.2 0) (-16 -16 -24) (16 16 32) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Blue Flag for CTF. --------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- -model="players/male/flag2.md2" -*/ - { - /* id */ IT_FLAG_BLUE, - /* classname */ ITEM_CTF_FLAG_BLUE, - /* pickup */ CTF_PickupFlag, - /* use */ nullptr, - /* drop */ CTF_DropFlag, - /* weaponthink */ nullptr, - /* pickup_sound */ "ctf/flagtk.wav", - /* world_model */ "players/male/flag2.md2", - /* world_model_flags */ EF_FLAG_BLUE, - /* view_model */ nullptr, - /* icon */ "i_ctf2", - /* use_name */ "Blue Flag", - /* pickup_name */ "$item_blue_flag", - /* pickup_name_definite */ "$item_blue_flag_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_NONE, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ 0, - /* precaches */ "ctf/flagcap.wav" - }, - -/* Disruptor Shield Tech */ - { - /* id */ IT_TECH_DISRUPTOR_SHIELD, - /* classname */ "item_tech1", - /* pickup */ Tech_Pickup, - /* use */ nullptr, - /* drop */ Tech_Drop, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/ctf/resistance/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "tech1", - /* use_name */ "Disruptor Shield", - /* pickup_name */ "$item_disruptor_shield", - /* pickup_name_definite */ "$item_disruptor_shield_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TECH | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_TECH_DISRUPTOR_SHIELD, - /* precaches */ "ctf/tech1.wav" - }, - -/* Power Amplifier Tech */ - { - /* id */ IT_TECH_POWER_AMP, - /* classname */ "item_tech2", - /* pickup */ Tech_Pickup, - /* use */ nullptr, - /* drop */ Tech_Drop, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/ctf/strength/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "tech2", - /* use_name */ "Power Amplifier", - /* pickup_name */ "$item_power_amplifier", - /* pickup_name_definite */ "$item_power_amplifier_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TECH | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_TECH_POWER_AMP, - /* precaches */ "ctf/tech2.wav ctf/tech2x.wav" - }, - -/* Time Accel Tech */ - { - /* id */ IT_TECH_TIME_ACCEL, - /* classname */ "item_tech3", - /* pickup */ Tech_Pickup, - /* use */ nullptr, - /* drop */ Tech_Drop, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/ctf/haste/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "tech3", - /* use_name */ "Time Accel", - /* pickup_name */ "$item_time_accel", - /* pickup_name_definite */ "$item_time_accel_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TECH | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_TECH_TIME_ACCEL, - /* precaches */ "ctf/tech3.wav" - }, - -/* AutoDoc Tech */ - { - /* id */ IT_TECH_AUTODOC, - /* classname */ "item_tech4", - /* pickup */ Tech_Pickup, - /* use */ nullptr, - /* drop */ Tech_Drop, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/ctf/regeneration/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "tech4", - /* use_name */ "AutoDoc", - /* pickup_name */ "$item_autodoc", - /* pickup_name_definite */ "$item_autodoc_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TECH | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_TECH_AUTODOC, - /* precaches */ "ctf/tech4.wav" - }, - -/*QUAKED ammo_shells_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/shells/large/tris.md2" -*/ - { - /* id */ IT_AMMO_SHELLS_LARGE , - /* classname */ "ammo_shells_large", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/shells/large/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_shells", - /* use_name */ "Large Shells", - /* pickup_name */ "Large Shells", - /* pickup_name_definite */ "Large Shells", - /* quantity */ 20, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_SHELLS - }, - -/*QUAKED ammo_shells_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/shells/small/tris.md2" -*/ - { - /* id */ IT_AMMO_SHELLS_SMALL, - /* classname */ "ammo_shells_small", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/shells/small/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_shells", - /* use_name */ "Small Shells", - /* pickup_name */ "Small Shells", - /* pickup_name_definite */ "Small Shells", - /* quantity */ 6, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_SHELLS - }, - -/*QUAKED ammo_bullets_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/bullets/large/tris.md2" -*/ - { - /* id */ IT_AMMO_BULLETS_LARGE, - /* classname */ "ammo_bullets_large", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/bullets/large/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_bullets", - /* use_name */ "Large Bullets", - /* pickup_name */ "Large Bullets", - /* pickup_name_definite */ "Large Bullets", - /* quantity */ 100, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_BULLETS - }, - -/*QUAKED ammo_bullets_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/bullets/small/tris.md2" -*/ - { - /* id */ IT_AMMO_BULLETS_SMALL, - /* classname */ "ammo_bullets_small", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/bullets/small/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_bullets", - /* use_name */ "Small Bullets", - /* pickup_name */ "Small Bullets", - /* pickup_name_definite */ "Small Bullets", - /* quantity */ 16, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_BULLETS - }, - -/*QUAKED ammo_cells_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/cells/large/tris.md2" -*/ - { - /* id */ IT_AMMO_CELLS_LARGE, - /* classname */ "ammo_cells_large", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/cells/large/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_cells", - /* use_name */ "Large Cells", - /* pickup_name */ "Large Cells", - /* pickup_name_definite */ "Large Cells", - /* quantity */ 100, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS - }, - -/*QUAKED ammo_cells_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/cells/small/tris.md2" -*/ - { - /* id */ IT_AMMO_CELLS_SMALL, - /* classname */ "ammo_cells_small", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/cells/small/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_cells", - /* use_name */ "Small Cells", - /* pickup_name */ "Small Cells", - /* pickup_name_definite */ "Small Cells", - /* quantity */ 20, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_CELLS - }, - -/*QUAKED ammo_rockets_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/rockets/small/tris.md2" -*/ - { - /* id */ IT_AMMO_ROCKETS_SMALL, - /* classname */ "ammo_rockets_small", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/rockets/small/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_rockets", - /* use_name */ "Small Rockets", - /* pickup_name */ "Small Rockets", - /* pickup_name_definite */ "Small Rockets", - /* quantity */ 2, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_ROCKETS - }, - -/*QUAKED ammo_slugs_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/slugs/large/tris.md2" -*/ - { - /* id */ IT_AMMO_SLUGS_LARGE, - /* classname */ "ammo_slugs_large", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/slugs/large/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_slugs", - /* use_name */ "Large Slugs", - /* pickup_name */ "Large Slugs", - /* pickup_name_definite */ "Large Slugs", - /* quantity */ 20, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_SLUGS - }, - -/*QUAKED ammo_slugs_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/slugs/small/tris.md2" -*/ - { - /* id */ IT_AMMO_SLUGS_SMALL, - /* classname */ "ammo_slugs_small", - /* pickup */ Pickup_Ammo, - /* use */ nullptr, - /* drop */ Drop_Ammo, - /* weaponthink */ nullptr, - /* pickup_sound */ "misc/am_pkup.wav", - /* world_model */ "models/vault/items/ammo/slugs/small/tris.md2", - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "a_slugs", - /* use_name */ "Small Slugs", - /* pickup_name */ "Small Slugs", - /* pickup_name_definite */ "Small Slugs", - /* quantity */ 3, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_AMMO, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ AMMO_SLUGS - }, - -/*QUAKED item_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/vault/items/ammo/nuke/tris.md2" -*/ - { - /* id */ IT_TELEPORTER, - /* classname */ "item_teleporter", - /* pickup */ Pickup_Teleporter, - /* use */ Use_Teleporter, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/vault/items/ammo/nuke/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_fixme", - /* use_name */ "Personal Teleporter", - /* pickup_name */ "Personal Teleporter", - /* pickup_name_definite */ "Personal Teleporter", - /* quantity */ 120, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_TIMED | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF - }, - -/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -model="models/items/invulner/tris.md2" -*/ - { - /* id */ IT_POWERUP_REGEN, - /* classname */ "item_regen", - /* pickup */ Pickup_Powerup, - /* use */ Use_Regeneration, - /* drop */ Drop_General, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/invulner/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_fixme", - /* use_name */ "Regeneration", - /* pickup_name */ "Regeneration", - /* pickup_name_definite */ "Regeneration", - /* quantity */ 60, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_REGEN, - /* precaches */ "items/protect.wav" - }, - -/*QUAKED item_foodcube (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Meaty cube o' health -model="models/objects/trapfx/tris.md2" -*/ - { - /* id */ IT_FOODCUBE, - /* classname */ "item_foodcube", - /* pickup */ Pickup_Health, - /* use */ nullptr, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/n_health.wav", - /* world_model */ "models/objects/trapfx/tris.md2", - /* world_model_flags */ EF_GIB, - /* view_model */ nullptr, - /* icon */ "i_health", - /* use_name */ "Meaty Cube", - /* pickup_name */ "Meaty Cube", - /* pickup_name_definite */ "Meaty Cube", - /* quantity */ 50, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_HEALTH, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ HEALTH_IGNORE_MAX - }, - -/*QUAKED item_ball (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP -Big ol' ball -models/items/ammo/grenades/medium/tris.md2" -*/ - { - /* id */ IT_BALL, - /* classname */ "item_ball", - /* pickup */ Pickup_Ball, - /* use */ Use_Ball, - /* drop */ Drop_Ball, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/ammo/grenades/medium/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "i_help", - /* use_name */ "Ball", - /* pickup_name */ "Ball", - /* pickup_name_definite */ "Ball", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_POWERUP| IF_POWERUP_WHEEL | IF_NOT_RANDOM, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_BALL, - /* precaches */ "", - /* sort_id */ -1 - }, - -/* Flashlight */ - { - /* id */ IT_FLASHLIGHT, - /* classname */ "item_flashlight", - /* pickup */ Pickup_General, - /* use */ Use_Flashlight, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ "items/pkup.wav", - /* world_model */ "models/items/flashlight/tris.md2", - /* world_model_flags */ EF_ROTATE | EF_BOB, - /* view_model */ nullptr, - /* icon */ "p_torch", - /* use_name */ "Flashlight", - /* pickup_name */ "$item_flashlight", - /* pickup_name_definite */ "$item_flashlight_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF | IF_NOT_RANDOM, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_FLASHLIGHT, - /* precaches */ "items/flashlight_on.wav items/flashlight_off.wav", - /* sort_id */ -1 - }, - -/* Compass */ - { - /* id */ IT_COMPASS, - /* classname */ "item_compass", - /* pickup */ nullptr, - /* use */ Use_Compass, - /* drop */ nullptr, - /* weaponthink */ nullptr, - /* pickup_sound */ nullptr, - /* world_model */ nullptr, - /* world_model_flags */ EF_NONE, - /* view_model */ nullptr, - /* icon */ "p_compass", - /* use_name */ "Compass", - /* pickup_name */ "$item_compass", - /* pickup_name_definite */ "$item_compass_def", - /* quantity */ 0, - /* ammo */ IT_NULL, - /* chain */ IT_NULL, - /* flags */ IF_STAY_COOP | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, - /* vwep_model */ nullptr, - /* armor_info */ nullptr, - /* tag */ POWERUP_COMPASS, - /* precaches */ "misc/help_marker.wav", - /* sort_id */ -2 - }, + /*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Power Cubes for warehouse. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/power/tris.md2" + */ + { + /* id */ IT_KEY_POWER_CUBE, + /* classname */ "key_power_cube", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/power/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_powercube", + /* use_name */ "Power Cube", + /* pickup_name */ "$item_power_cube", + /* pickup_name_definite */ "$item_power_cube_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_explosive_charges (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH x x x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Explosive Charges - for N64. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/n64/charge/tris.md2" + */ + { + /* id */ IT_KEY_EXPLOSIVE_CHARGES, + /* classname */ "key_explosive_charges", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/n64/charge/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "n64/i_charges", + /* use_name */ "Explosive Charges", + /* pickup_name */ "$item_explosive_charges", + /* pickup_name_definite */ "$item_explosive_charges_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_yellow_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Normal door key - Yellow - for N64. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/n64/yellow_key/tris.md2" + */ + { + /* id */ IT_KEY_YELLOW, + /* classname */ "key_yellow_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/n64/yellow_key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "n64/i_yellow_key", + /* use_name */ "Yellow Key", + /* pickup_name */ "$item_yellow_key", + /* pickup_name_definite */ "$item_yellow_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_power_core (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Power Core key - for N64. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/n64/power_core/tris.md2" + */ + { + /* id */ IT_KEY_POWER_CORE, + /* classname */ "key_power_core", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/n64/power_core/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_pyramid", + /* use_name */ "Power Core", + /* pickup_name */ "$item_power_core", + /* pickup_name_definite */ "$item_power_core_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Key for the entrance of jail3. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/pyramid/tris.md2" + */ + { + /* id */ IT_KEY_PYRAMID, + /* classname */ "key_pyramid", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/pyramid/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_pyramid", + /* use_name */ "Pyramid Key", + /* pickup_name */ "$item_pyramid_key", + /* pickup_name_definite */ "$item_pyramid_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Key for the city computer. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/spinner/tris.md2" + */ + { + /* id */ IT_KEY_DATA_SPINNER, + /* classname */ "key_data_spinner", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/spinner/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_dataspin", + /* use_name */ "Data Spinner", + /* pickup_name */ "$item_data_spinner", + /* pickup_name_definite */ "$item_data_spinner_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Security pass for the security level. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/pass/tris.md2" + */ + { + /* id */ IT_KEY_PASS, + /* classname */ "key_pass", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/pass/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_security", + /* use_name */ "Security Pass", + /* pickup_name */ "$item_security_pass", + /* pickup_name_definite */ "$item_security_pass_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Normal door key - Blue. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/key/tris.md2" + */ + { + /* id */ IT_KEY_BLUE_KEY, + /* classname */ "key_blue_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_bluekey", + /* use_name */ "Blue Key", + /* pickup_name */ "$item_blue_key", + /* pickup_name_definite */ "$item_blue_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Normal door key - Red. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/red_key/tris.md2" + */ + { + /* id */ IT_KEY_RED_KEY, + /* classname */ "key_red_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/red_key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_redkey", + /* use_name */ "Red Key", + /* pickup_name */ "$item_red_key", + /* pickup_name_definite */ "$item_red_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_green_key (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Normal door key - Green. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/keys/green_key/tris.md2" + */ + { + /* id */ IT_KEY_GREEN_KEY, + /* classname */ "key_green_key", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/green_key/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "k_green", + /* use_name */ "Green Key", + /* pickup_name */ "$item_green_key", + /* pickup_name_definite */ "$item_green_key_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Key - Tank Commander's Head. + model="models/monsters/commandr/head/tris.md2" + */ + { + /* id */ IT_KEY_COMMANDER_HEAD, + /* classname */ "key_commander_head", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/monsters/commandr/head/tris.md2", + /* world_model_flags */ EF_GIB, + /* view_model */ nullptr, + /* icon */ "k_comhead", + /* use_name */ "Commander's Head", + /* pickup_name */ "$item_commanders_head", + /* pickup_name_definite */ "$item_commanders_head_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Key - Airstrike Target for strike. + model="models/items/keys/target/tris.md2" + */ + { + /* id */ IT_KEY_AIRSTRIKE, + /* classname */ "key_airstrike_target", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/keys/target/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_airstrike", + /* use_name */ "Airstrike Marker", + /* pickup_name */ "$item_airstrike_marker", + /* pickup_name_definite */ "$item_airstrike_marker_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY + }, + + /*QUAKED key_nuke_container (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_nuke/tris.md2" + */ + { + /* id */ IT_KEY_NUKE_CONTAINER, + /* classname */ "key_nuke_container", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/weapons/g_nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_contain", + /* use_name */ "Antimatter Pod", + /* pickup_name */ "$item_antimatter_pod", + /* pickup_name_definite */ "$item_antimatter_pod_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY, + }, + + /*QUAKED key_nuke (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/weapons/g_nuke/tris.md2" + */ + { + /* id */ IT_KEY_NUKE, + /* classname */ "key_nuke", + /* pickup */ Pickup_Key, + /* use */ nullptr, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/weapons/g_nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_nuke", + /* use_name */ "Antimatter Bomb", + /* pickup_name */ "$item_antimatter_bomb", + /* pickup_name_definite */ "$item_antimatter_bomb_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_KEY, + }, + + /*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Health - Stimpack. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/healing/stimpack/tris.md2" + */ + // Paril: split the healths up so they are always valid classnames + { + /* id */ IT_HEALTH_SMALL, + /* classname */ "item_health_small", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/s_health.wav", + /* world_model */ "models/items/healing/stimpack/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Health", + /* pickup_name */ "$item_stimpack", + /* pickup_name_definite */ "$item_stimpack_def", + /* quantity */ 2, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ HEALTH_IGNORE_MAX + }, + + /*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Health - First Aid. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/healing/medium/tris.md2" + */ + { + /* id */ IT_HEALTH_MEDIUM, + /* classname */ "item_health", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/n_health.wav", + /* world_model */ "models/items/healing/medium/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Health", + /* pickup_name */ "$item_small_medkit", + /* pickup_name_definite */ "$item_small_medkit_def", + /* quantity */ 10, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH + }, + + /*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Health - Medkit. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/healing/large/tris.md2" + */ + { + /* id */ IT_HEALTH_LARGE, + /* classname */ "item_health_large", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/l_health.wav", + /* world_model */ "models/items/healing/large/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Health", + /* pickup_name */ "$item_large_medkit", + /* pickup_name_definite */ "$item_large_medkit", + /* quantity */ 25, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH + }, + + /*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Health - Mega Health. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="models/items/mega_h/tris.md2" + */ + { + /* id */ IT_HEALTH_MEGA, + /* classname */ "item_health_mega", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/m_health.wav", + /* world_model */ "models/items/mega_h/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "p_megahealth", + /* use_name */ "Mega Health", + /* pickup_name */ "$item_mega_health", + /* pickup_name_definite */ "$item_mega_health_def", + /* quantity */ 100, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ HEALTH_IGNORE_MAX | HEALTH_TIMED + }, + + /*QUAKED item_flag_team_red (1 0.2 0) (-16 -16 -24) (16 16 32) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Red Flag for CTF. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="players/male/flag1.md2" + */ + { + /* id */ IT_FLAG_RED, + /* classname */ ITEM_CTF_FLAG_RED, + /* pickup */ CTF_PickupFlag, + /* use */ nullptr, + /* drop */ CTF_DropFlag, //Should this be null if we don't want players to drop it manually? + /* weaponthink */ nullptr, + /* pickup_sound */ "ctf/flagtk.wav", + /* world_model */ "players/male/flag1.md2", + /* world_model_flags */ EF_FLAG_RED, + /* view_model */ nullptr, + /* icon */ "i_ctf1", + /* use_name */ "Red Flag", + /* pickup_name */ "$item_red_flag", + /* pickup_name_definite */ "$item_red_flag_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_NONE, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "ctf/flagcap.wav" + }, + + /*QUAKED item_flag_team_blue (1 0.2 0) (-16 -16 -24) (16 16 32) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Blue Flag for CTF. + -------- MODEL FOR RADIANT ONLY - DO NOT SET THIS AS A KEY -------- + model="players/male/flag2.md2" + */ + { + /* id */ IT_FLAG_BLUE, + /* classname */ ITEM_CTF_FLAG_BLUE, + /* pickup */ CTF_PickupFlag, + /* use */ nullptr, + /* drop */ CTF_DropFlag, + /* weaponthink */ nullptr, + /* pickup_sound */ "ctf/flagtk.wav", + /* world_model */ "players/male/flag2.md2", + /* world_model_flags */ EF_FLAG_BLUE, + /* view_model */ nullptr, + /* icon */ "i_ctf2", + /* use_name */ "Blue Flag", + /* pickup_name */ "$item_blue_flag", + /* pickup_name_definite */ "$item_blue_flag_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_NONE, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ 0, + /* precaches */ "ctf/flagcap.wav" + }, + + /* Disruptor Shield Tech */ + { + /* id */ IT_TECH_DISRUPTOR_SHIELD, + /* classname */ "item_tech1", + /* pickup */ Tech_Pickup, + /* use */ nullptr, + /* drop */ Tech_Drop, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/resistance/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech1", + /* use_name */ "Disruptor Shield", + /* pickup_name */ "$item_disruptor_shield", + /* pickup_name_definite */ "$item_disruptor_shield_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH_DISRUPTOR_SHIELD, + /* precaches */ "ctf/tech1.wav" + }, + + /* Power Amplifier Tech */ + { + /* id */ IT_TECH_POWER_AMP, + /* classname */ "item_tech2", + /* pickup */ Tech_Pickup, + /* use */ nullptr, + /* drop */ Tech_Drop, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/strength/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech2", + /* use_name */ "Power Amplifier", + /* pickup_name */ "$item_power_amplifier", + /* pickup_name_definite */ "$item_power_amplifier_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH_POWER_AMP, + /* precaches */ "ctf/tech2.wav ctf/tech2x.wav" + }, + + /* Time Accel Tech */ + { + /* id */ IT_TECH_TIME_ACCEL, + /* classname */ "item_tech3", + /* pickup */ Tech_Pickup, + /* use */ nullptr, + /* drop */ Tech_Drop, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/haste/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech3", + /* use_name */ "Time Accel", + /* pickup_name */ "$item_time_accel", + /* pickup_name_definite */ "$item_time_accel_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH_TIME_ACCEL, + /* precaches */ "ctf/tech3.wav" + }, + + /* AutoDoc Tech */ + { + /* id */ IT_TECH_AUTODOC, + /* classname */ "item_tech4", + /* pickup */ Tech_Pickup, + /* use */ nullptr, + /* drop */ Tech_Drop, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/ctf/regeneration/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "tech4", + /* use_name */ "AutoDoc", + /* pickup_name */ "$item_autodoc", + /* pickup_name_definite */ "$item_autodoc_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TECH | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_TECH_AUTODOC, + /* precaches */ "ctf/tech4.wav" + }, + + /*QUAKED ammo_shells_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/shells/large/tris.md2" + */ + { + /* id */ IT_AMMO_SHELLS_LARGE , + /* classname */ "ammo_shells_large", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/shells/large/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_shells", + /* use_name */ "Large Shells", + /* pickup_name */ "Large Shells", + /* pickup_name_definite */ "Large Shells", + /* quantity */ 20, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SHELLS + }, + + /*QUAKED ammo_shells_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/shells/small/tris.md2" + */ + { + /* id */ IT_AMMO_SHELLS_SMALL, + /* classname */ "ammo_shells_small", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/shells/small/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_shells", + /* use_name */ "Small Shells", + /* pickup_name */ "Small Shells", + /* pickup_name_definite */ "Small Shells", + /* quantity */ 6, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SHELLS + }, + + /*QUAKED ammo_bullets_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/bullets/large/tris.md2" + */ + { + /* id */ IT_AMMO_BULLETS_LARGE, + /* classname */ "ammo_bullets_large", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/bullets/large/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_bullets", + /* use_name */ "Large Bullets", + /* pickup_name */ "Large Bullets", + /* pickup_name_definite */ "Large Bullets", + /* quantity */ 100, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_BULLETS + }, + + /*QUAKED ammo_bullets_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/bullets/small/tris.md2" + */ + { + /* id */ IT_AMMO_BULLETS_SMALL, + /* classname */ "ammo_bullets_small", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/bullets/small/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_bullets", + /* use_name */ "Small Bullets", + /* pickup_name */ "Small Bullets", + /* pickup_name_definite */ "Small Bullets", + /* quantity */ 16, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_BULLETS + }, + + /*QUAKED ammo_cells_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/cells/large/tris.md2" + */ + { + /* id */ IT_AMMO_CELLS_LARGE, + /* classname */ "ammo_cells_large", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/cells/large/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_cells", + /* use_name */ "Large Cells", + /* pickup_name */ "Large Cells", + /* pickup_name_definite */ "Large Cells", + /* quantity */ 100, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS + }, + + /*QUAKED ammo_cells_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/cells/small/tris.md2" + */ + { + /* id */ IT_AMMO_CELLS_SMALL, + /* classname */ "ammo_cells_small", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/cells/small/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_cells", + /* use_name */ "Small Cells", + /* pickup_name */ "Small Cells", + /* pickup_name_definite */ "Small Cells", + /* quantity */ 20, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_CELLS + }, + + /*QUAKED ammo_rockets_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/rockets/small/tris.md2" + */ + { + /* id */ IT_AMMO_ROCKETS_SMALL, + /* classname */ "ammo_rockets_small", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/rockets/small/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_rockets", + /* use_name */ "Small Rockets", + /* pickup_name */ "Small Rockets", + /* pickup_name_definite */ "Small Rockets", + /* quantity */ 2, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_ROCKETS + }, + + /*QUAKED ammo_slugs_large (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/slugs/large/tris.md2" + */ + { + /* id */ IT_AMMO_SLUGS_LARGE, + /* classname */ "ammo_slugs_large", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/slugs/large/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_slugs", + /* use_name */ "Large Slugs", + /* pickup_name */ "Large Slugs", + /* pickup_name_definite */ "Large Slugs", + /* quantity */ 20, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SLUGS + }, + + /*QUAKED ammo_slugs_small (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/slugs/small/tris.md2" + */ + { + /* id */ IT_AMMO_SLUGS_SMALL, + /* classname */ "ammo_slugs_small", + /* pickup */ Pickup_Ammo, + /* use */ nullptr, + /* drop */ Drop_Ammo, + /* weaponthink */ nullptr, + /* pickup_sound */ "misc/am_pkup.wav", + /* world_model */ "models/vault/items/ammo/slugs/small/tris.md2", + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "a_slugs", + /* use_name */ "Small Slugs", + /* pickup_name */ "Small Slugs", + /* pickup_name_definite */ "Small Slugs", + /* quantity */ 3, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_AMMO, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ AMMO_SLUGS + }, + + /*QUAKED item_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/vault/items/ammo/nuke/tris.md2" + */ + { + /* id */ IT_TELEPORTER, + /* classname */ "item_teleporter", + /* pickup */ Pickup_Teleporter, + /* use */ Use_Teleporter, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/vault/items/ammo/nuke/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_fixme", + /* use_name */ "Personal Teleporter", + /* pickup_name */ "Personal Teleporter", + /* pickup_name_definite */ "Personal Teleporter", + /* quantity */ 120, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_TIMED | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF + }, + + /*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + model="models/items/invulner/tris.md2" + */ + { + /* id */ IT_POWERUP_REGEN, + /* classname */ "item_regen", + /* pickup */ Pickup_Powerup, + /* use */ Use_Regeneration, + /* drop */ Drop_General, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/invulner/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_fixme", + /* use_name */ "Regeneration", + /* pickup_name */ "Regeneration", + /* pickup_name_definite */ "Regeneration", + /* quantity */ 60, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_POWERUP | IF_POWERUP_WHEEL, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_REGEN, + /* precaches */ "items/protect.wav" + }, + + /*QUAKED item_foodcube (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Meaty cube o' health + model="models/objects/trapfx/tris.md2" + */ + { + /* id */ IT_FOODCUBE, + /* classname */ "item_foodcube", + /* pickup */ Pickup_Health, + /* use */ nullptr, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/n_health.wav", + /* world_model */ "models/objects/trapfx/tris.md2", + /* world_model_flags */ EF_GIB, + /* view_model */ nullptr, + /* icon */ "i_health", + /* use_name */ "Meaty Cube", + /* pickup_name */ "Meaty Cube", + /* pickup_name_definite */ "Meaty Cube", + /* quantity */ 50, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_HEALTH, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ HEALTH_IGNORE_MAX + }, + + /*QUAKED item_ball (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN x x SUSPENDED x x x x NOT_EASY NOT_MEDIUM NOT_HARD NOT_DM NOT_COOP + Big ol' ball + models/items/ammo/grenades/medium/tris.md2" + */ + { + /* id */ IT_BALL, + /* classname */ "item_ball", + /* pickup */ Pickup_Ball, + /* use */ Use_Ball, + /* drop */ Drop_Ball, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/ammo/grenades/medium/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "i_help", + /* use_name */ "Ball", + /* pickup_name */ "Ball", + /* pickup_name_definite */ "Ball", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_POWERUP | IF_POWERUP_WHEEL | IF_NOT_RANDOM, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_BALL, + /* precaches */ "", + /* sort_id */ -1 + }, + + /* Flashlight */ + { + /* id */ IT_FLASHLIGHT, + /* classname */ "item_flashlight", + /* pickup */ Pickup_General, + /* use */ Use_Flashlight, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ "items/pkup.wav", + /* world_model */ "models/items/flashlight/tris.md2", + /* world_model_flags */ EF_ROTATE | EF_BOB, + /* view_model */ nullptr, + /* icon */ "p_torch", + /* use_name */ "Flashlight", + /* pickup_name */ "$item_flashlight", + /* pickup_name_definite */ "$item_flashlight_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF | IF_NOT_RANDOM, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_FLASHLIGHT, + /* precaches */ "items/flashlight_on.wav items/flashlight_off.wav", + /* sort_id */ -1 + }, + + /* Compass */ + { + /* id */ IT_COMPASS, + /* classname */ "item_compass", + /* pickup */ nullptr, + /* use */ Use_Compass, + /* drop */ nullptr, + /* weaponthink */ nullptr, + /* pickup_sound */ nullptr, + /* world_model */ nullptr, + /* world_model_flags */ EF_NONE, + /* view_model */ nullptr, + /* icon */ "p_compass", + /* use_name */ "Compass", + /* pickup_name */ "$item_compass", + /* pickup_name_definite */ "$item_compass_def", + /* quantity */ 0, + /* ammo */ IT_NULL, + /* chain */ IT_NULL, + /* flags */ IF_STAY_COOP | IF_POWERUP_WHEEL | IF_POWERUP_ONOFF, + /* vwep_model */ nullptr, + /* armor_info */ nullptr, + /* tag */ POWERUP_COMPASS, + /* precaches */ "misc/help_marker.wav", + /* sort_id */ -2 + }, }; // clang-format on @@ -6188,13 +6238,13 @@ void InitItems() { if (!itemlist[i].chain) continue; - gitem_t *item = &itemlist[i]; + gitem_t* item = &itemlist[i]; // already initialized if (item->chain_next) continue; - gitem_t *chain_item = &itemlist[item->chain]; + gitem_t* chain_item = &itemlist[item->chain]; if (!chain_item) gi.Com_ErrorFmt("Invalid item chain {} for {}", (int32_t)item->chain, item->pickup_name); @@ -6205,7 +6255,7 @@ void InitItems() { // if we're not the first in chain, add us now if (chain_item != item) { - gitem_t *c; + gitem_t* c; // end of chain is one whose chain_next points to chain_item for (c = chain_item; c->chain_next != chain_item; c = c->chain_next) @@ -6218,7 +6268,7 @@ void InitItems() { } // set up ammo - for (auto &it : itemlist) { + for (auto& it : itemlist) { if ((it.flags & IF_AMMO) && it.tag >= AMMO_BULLETS && it.tag < AMMO_MAX) { if (it.id <= IT_AMMO_ROUNDS) ammolist[it.tag] = ⁢ @@ -6228,7 +6278,7 @@ void InitItems() { } // in coop or DM with Weapons' Stay, remove drop ptr - for (auto &it : itemlist) { + for (auto& it : itemlist) { if (coop->integer) if (!P_UseCoopInstancedItems() && (it.flags & IF_STAY_COOP)) it.drop = nullptr; @@ -6240,7 +6290,7 @@ void InitItems() { G_CanDropItem =============== */ -static inline bool G_CanDropItem(const gitem_t &item) { +static inline bool G_CanDropItem(const gitem_t& item) { if (!item.drop) return false; else if ((item.flags & IF_WEAPON) && !(item.flags & IF_AMMO) && deathmatch->integer && g_dm_weapons_stay->integer) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index d762749..47e1638 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -19,7 +19,7 @@ Searches beginning at the entity after from, or the beginning if nullptr nullptr will be returned if the end of the list is reached. ============= */ -gentity_t *G_Find(gentity_t *from, std::function matcher) { +gentity_t* G_Find(gentity_t* from, std::function matcher) { if (!from) from = g_entities; else @@ -44,7 +44,7 @@ Returns entities that have origins within a spherical area findradius (origin, radius) ================= */ -gentity_t *findradius(gentity_t *from, const vec3_t &org, float rad) { +gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { vec3_t eorg; int j; @@ -80,9 +80,9 @@ nullptr will be returned if the end of the list is reached. ============= */ -gentity_t *G_PickTarget(const char *targetname) { - std::vector choices; - gentity_t *ent = nullptr; +gentity_t* G_PickTarget(const char* targetname) { + std::vector choices; + gentity_t* ent = nullptr; if (!targetname) { gi.Com_PrintFmt("{}: called with nullptr targetname.\n", __FUNCTION__); @@ -103,13 +103,13 @@ gentity_t *G_PickTarget(const char *targetname) { } return G_SelectRandomTarget( - choices, - [](size_t max_index) { - return static_cast(irandom(static_cast(max_index))); - }); + choices, + [](size_t max_index) { + return static_cast(irandom(static_cast(max_index))); + }); } -static THINK(Think_Delay) (gentity_t *ent) -> void { +static THINK(Think_Delay) (gentity_t* ent) -> void { G_UseTargets(ent, ent->activator); G_FreeEntity(ent); } @@ -121,7 +121,7 @@ G_PrintActivationMessage Prints activation messaging and plays optional sounds when an entity is used. ============= */ -void G_PrintActivationMessage(gentity_t *ent, gentity_t *activator, bool coop_global) { +void G_PrintActivationMessage(gentity_t* ent, gentity_t* activator, bool coop_global) { if (!ent || !ent->message) return; @@ -157,7 +157,7 @@ Broadcast a friendly message to active teammates or, in non-team modes, all active players. ============= */ -void BroadcastFriendlyMessage(team_t team, const char *msg) { +void BroadcastFriendlyMessage(team_t team, const char* msg) { if (!FriendlyMessageHasText(msg)) return; @@ -166,10 +166,11 @@ void BroadcastFriendlyMessage(team_t team, const char *msg) { if (!playing) { if (!Teams()) continue; - gentity_t *follow = ce->client->follow_target; + gentity_t* follow = ce->client->follow_target; if (!follow || !follow->client || follow->client->sess.team != team) continue; - } else if (Teams() && ce->client->sess.team != team) { + } + else if (Teams() && ce->client->sess.team != team) { continue; } gi.LocClient_Print(ce, PRINT_HIGH, G_Fmt("{}{}", playing && ce->client->sess.team != TEAM_SPECTATOR ? "[TEAM]: " : "", msg).data()); @@ -183,7 +184,7 @@ BroadcastTeamMessage Broadcast a message to all clients actively playing for the specified team. ============= */ -void BroadcastTeamMessage(team_t team, print_type_t level, const char *msg) { +void BroadcastTeamMessage(team_t team, print_type_t level, const char* msg) { for (auto ce : active_clients()) { if (!ClientIsPlaying(ce->client)) continue; @@ -195,7 +196,7 @@ void BroadcastTeamMessage(team_t team, print_type_t level, const char *msg) { } } -void G_MonsterKilled(gentity_t *self); +void G_MonsterKilled(gentity_t* self); /* ============================== @@ -213,8 +214,8 @@ match (string)self.target and call their .use function ============================== */ -void G_UseTargets(gentity_t *ent, gentity_t *activator) { - gentity_t *t; +void G_UseTargets(gentity_t* ent, gentity_t* activator) { + gentity_t* t; if (!ent) return; @@ -254,7 +255,7 @@ void G_UseTargets(gentity_t *ent, gentity_t *activator) { if (t->teammaster) { // if this entity is part of a chain, cleanly remove it if (t->flags & FL_TEAMSLAVE) { - for (gentity_t *master = t->teammaster; master; master = master->teamchain) { + for (gentity_t* master = t->teammaster; master; master = master->teamchain) { if (master->teamchain == t) { master->teamchain = t->teamchain; break; @@ -265,13 +266,13 @@ void G_UseTargets(gentity_t *ent, gentity_t *activator) { else if (t->flags & FL_TEAMMASTER) { t->teammaster->flags &= ~FL_TEAMMASTER; - gentity_t *new_master = t->teammaster->teamchain; + gentity_t* new_master = t->teammaster->teamchain; if (new_master) { new_master->flags |= FL_TEAMMASTER; new_master->flags &= ~FL_TEAMSLAVE; - for (gentity_t *m = new_master; m; m = m->teamchain) + for (gentity_t* m = new_master; m; m = m->teamchain) m->teammaster = new_master; } } @@ -306,7 +307,8 @@ void G_UseTargets(gentity_t *ent, gentity_t *activator) { if (t == ent) { gi.Com_PrintFmt("{}: WARNING: Entity used itself.\n", __FUNCTION__); - } else { + } + else { if (t->use) t->use(t, ent, activator); } @@ -323,33 +325,35 @@ void G_UseTargets(gentity_t *ent, gentity_t *activator) { G_SetMovedir =============== */ -void G_SetMovedir(vec3_t &angles, vec3_t &movedir) { - static vec3_t VEC_UP = { 0, -1, 0 }; - static vec3_t MOVEDIR_UP = { 0, 0, 1 }; - static vec3_t VEC_DOWN = { 0, -2, 0 }; - static vec3_t MOVEDIR_DOWN = { 0, 0, -1 }; +void G_SetMovedir(vec3_t& angles, vec3_t& movedir) { + static vec3_t VEC_UP = { 0, -1, 0 }; + static vec3_t MOVEDIR_UP = { 0, 0, 1 }; + static vec3_t VEC_DOWN = { 0, -2, 0 }; + static vec3_t MOVEDIR_DOWN = { 0, 0, -1 }; if (angles == VEC_UP) { movedir = MOVEDIR_UP; - } else if (angles == VEC_DOWN) { + } + else if (angles == VEC_DOWN) { movedir = MOVEDIR_DOWN; - } else { + } + else { AngleVectors(angles, movedir, nullptr, nullptr); } angles = {}; } -char *G_CopyString(const char *in, int32_t tag) { +char* G_CopyString(const char* in, int32_t tag) { if (!in) return nullptr; const size_t amt = strlen(in) + 1; - char *const out = static_cast(gi.TagMalloc(amt, tag)); + char* const out = static_cast(gi.TagMalloc(amt, tag)); Q_strlcpy(out, in, amt); return out; } -void G_InitGentity(gentity_t *e) { +void G_InitGentity(gentity_t* e) { // FIXME - // this fixes a bug somewhere that is setting "nextthink" for an entity that has // already been released. nextthink is being set to FRAME_TIME_S after level.time, @@ -378,8 +382,8 @@ instead of being removed and recreated, which can cause interpolated angles and bad trails. ================= */ -gentity_t *G_Spawn() { - gentity_t *e = &g_entities[game.maxclients + 1]; +gentity_t* G_Spawn() { + gentity_t* e = &g_entities[game.maxclients + 1]; size_t i; for (i = game.maxclients + 1; i < globals.num_entities; i++, e++) { @@ -407,7 +411,7 @@ G_FreeEntity Marks the entity as free ================= */ -THINK(G_FreeEntity) (gentity_t *ed) -> void { +THINK(G_FreeEntity) (gentity_t* ed) -> void { // already freed if (!ed->inuse) return; @@ -434,7 +438,7 @@ THINK(G_FreeEntity) (gentity_t *ed) -> void { ed->sv.init = false; } -BoxEntitiesResult_t G_TouchTriggers_BoxFilter(gentity_t *hit, void *) { +BoxEntitiesResult_t G_TouchTriggers_BoxFilter(gentity_t* hit, void*) { if (!hit->touch) return BoxEntitiesResult_t::Skip; @@ -447,10 +451,10 @@ G_TouchTriggers ============ */ -void G_TouchTriggers(gentity_t *ent) { +void G_TouchTriggers(gentity_t* ent) { int num; - static gentity_t *touch[MAX_ENTITIES]; - gentity_t *hit; + static gentity_t* touch[MAX_ENTITIES]; + gentity_t* hit; if (ent->client && ent->client->eliminated); else @@ -478,9 +482,9 @@ void G_TouchTriggers(gentity_t *ent) { // [Paril-KEX] scan for projectiles between our movement positions // to see if we need to collide against them -void G_TouchProjectiles(gentity_t *ent, vec3_t previous_origin) { +void G_TouchProjectiles(gentity_t* ent, vec3_t previous_origin) { struct skipped_projectile { - gentity_t *projectile; + gentity_t* projectile; int32_t spawn_count; }; // a bit ugly, but we'll store projectiles we are ignoring here. @@ -506,7 +510,7 @@ void G_TouchProjectiles(gentity_t *ent, vec3_t previous_origin) { G_Impact(ent, tr); } - for (auto &skip : skipped) + for (auto& skip : skipped) if (skip.projectile->inuse && skip.projectile->spawn_count == skip.spawn_count) skip.projectile->svflags |= SVF_PROJECTILE; @@ -530,14 +534,14 @@ of ent. ================= */ -BoxEntitiesResult_t KillBox_BoxFilter(gentity_t *hit, void *) { +BoxEntitiesResult_t KillBox_BoxFilter(gentity_t* hit, void*) { if (!hit->solid || !hit->takedamage || hit->solid == SOLID_TRIGGER) return BoxEntitiesResult_t::Skip; return BoxEntitiesResult_t::Keep; } -bool KillBox(gentity_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping) { +bool KillBox(gentity_t* ent, bool from_spawning, mod_id_t mod, bool bsp_clipping) { // don't telefrag as spectator or noclip player... if (ent->movetype == MOVETYPE_NOCLIP || ent->movetype == MOVETYPE_FREECAM) return true; @@ -549,8 +553,8 @@ bool KillBox(gentity_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping mask &= ~CONTENTS_PLAYER; int i, num; - static gentity_t *touch[MAX_ENTITIES]; - gentity_t *hit; + static gentity_t* touch[MAX_ENTITIES]; + gentity_t* hit; num = gi.BoxEntities(ent->absmin, ent->absmax, touch, MAX_ENTITIES, AREA_SOLID, KillBox_BoxFilter, nullptr); @@ -588,7 +592,7 @@ bool KillBox(gentity_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping /*--------------------------------------------------------------------------*/ -const char *Teams_TeamName(team_t team) { +const char* Teams_TeamName(team_t team) { switch (team) { case TEAM_RED: return "RED"; @@ -602,7 +606,7 @@ const char *Teams_TeamName(team_t team) { return "NONE"; } -const char *Teams_OtherTeamName(team_t team) { +const char* Teams_OtherTeamName(team_t team) { switch (team) { case TEAM_RED: return "BLUE"; @@ -622,15 +626,15 @@ team_t Teams_OtherTeam(team_t team) { return TEAM_SPECTATOR; // invalid value } -constexpr const char *TEAM_RED_SKIN = "ctf_r"; -constexpr const char *TEAM_BLUE_SKIN = "ctf_b"; +constexpr const char* TEAM_RED_SKIN = "ctf_r"; +constexpr const char* TEAM_BLUE_SKIN = "ctf_b"; /* ================= G_AssignPlayerSkin ================= */ -void G_AssignPlayerSkin(gentity_t *ent, const char *s) { +void G_AssignPlayerSkin(gentity_t* ent, const char* s) { int playernum = ent - g_entities - 1; std::string_view t(s); @@ -661,7 +665,7 @@ void G_AssignPlayerSkin(gentity_t *ent, const char *s) { G_AdjustPlayerScore =================== */ -void G_AdjustPlayerScore(gclient_t *cl, int32_t offset, bool adjust_team, int32_t team_offset) { +void G_AdjustPlayerScore(gclient_t* cl, int32_t offset, bool adjust_team, int32_t team_offset) { if (!cl) return; if (cl->sess.is_banned) @@ -687,7 +691,7 @@ void G_AdjustPlayerScore(gclient_t *cl, int32_t offset, bool adjust_team, int32_ Horde_AdjustPlayerScore =================== */ -void Horde_AdjustPlayerScore(gclient_t *cl, int32_t offset) { +void Horde_AdjustPlayerScore(gclient_t* cl, int32_t offset) { if (notGT(GT_HORDE)) return; if (!cl || !cl->pers.connected) return; @@ -702,7 +706,7 @@ void Horde_AdjustPlayerScore(gclient_t *cl, int32_t offset) { G_SetPlayerScore =================== */ -void G_SetPlayerScore(gclient_t *cl, int32_t value) { +void G_SetPlayerScore(gclient_t* cl, int32_t value) { if (!cl) return; if (IsScoringDisabled()) @@ -769,36 +773,46 @@ G_PlaceString Adapted from Quake III =================== */ -const char *G_PlaceString(int rank) { +const char* G_PlaceString(int rank) { static char str[64]; - const char *s, *t; + const char* s, * t; if (rank & RANK_TIED_FLAG) { rank &= ~RANK_TIED_FLAG; t = "Tied for "; - } else { + } + else { t = ""; } if (rank == 1) { s = "1st"; - } else if (rank == 2) { + } + else if (rank == 2) { s = "2nd"; - } else if (rank == 3) { + } + else if (rank == 3) { s = "3rd"; - } else if (rank == 11) { + } + else if (rank == 11) { s = "11th"; - } else if (rank == 12) { + } + else if (rank == 12) { s = "12th"; - } else if (rank == 13) { + } + else if (rank == 13) { s = "13th"; - } else if (rank % 10 == 1) { + } + else if (rank % 10 == 1) { s = G_Fmt("{}st", rank).data(); - } else if (rank % 10 == 2) { + } + else if (rank % 10 == 2) { s = G_Fmt("{}nd", rank).data(); - } else if (rank % 10 == 3) { + } + else if (rank % 10 == 3) { s = G_Fmt("{}rd", rank).data(); - } else { + } + else { s = G_Fmt("{}th", rank).data(); } Q_strlcpy(str, G_Fmt("{}{}", t, s).data(), sizeof(str)); @@ -816,7 +830,7 @@ bool ItemSpawnsEnabled() { } -static void loc_buildboxpoints(vec3_t(&p)[8], const vec3_t &org, const vec3_t &mins, const vec3_t &maxs) { +static void loc_buildboxpoints(vec3_t(&p)[8], const vec3_t& org, const vec3_t& mins, const vec3_t& maxs) { p[0] = org + mins; p[1] = p[0]; p[1][0] -= mins[0]; @@ -835,7 +849,7 @@ static void loc_buildboxpoints(vec3_t(&p)[8], const vec3_t &org, const vec3_t &m p[7][1] -= maxs[1]; } -bool loc_CanSee(gentity_t *targ, gentity_t *inflictor) { +bool loc_CanSee(gentity_t* targ, gentity_t* inflictor) { trace_t trace; vec3_t targpoints[8]; int i; @@ -851,7 +865,7 @@ bool loc_CanSee(gentity_t *targ, gentity_t *inflictor) { viewpoint[2] += inflictor->viewheight; for (i = 0; i < 8; i++) { - trace = gi.traceline(viewpoint, targpoints[i], inflictor, CONTENTS_MIST|MASK_WATER|MASK_SOLID); + trace = gi.traceline(viewpoint, targpoints[i], inflictor, CONTENTS_MIST | MASK_WATER | MASK_SOLID); if (trace.fraction == 1.0f) return true; } @@ -871,7 +885,7 @@ G_TimeString Format a match timer string with minute precision. ============= */ -const char *G_TimeString(const int msec, bool state) { +const char* G_TimeString(const int msec, bool state) { static char buffer[32]; if (state) { if (level.match_state < matchst_t::MATCH_COUNTDOWN) @@ -892,7 +906,8 @@ const char *G_TimeString(const int msec, bool state) { if (hours > 0) { G_FmtTo(buffer, "{}{}:{:02}:{:02}", msec < 1000 ? "-" : "", hours, mins, seconds); - } else { + } + else { G_FmtTo(buffer, "{}{:02}:{:02}", msec < 1000 ? "-" : "", mins, seconds); } @@ -905,7 +920,7 @@ G_TimeStringMs Format a match timer string with millisecond precision. ============= */ -const char *G_TimeStringMs(const int msec, bool state) { +const char* G_TimeStringMs(const int msec, bool state) { static char buffer[32]; if (state) { if (level.match_state < matchst_t::MATCH_COUNTDOWN) @@ -926,24 +941,28 @@ const char *G_TimeStringMs(const int msec, bool state) { if (hours > 0) { G_FmtTo(buffer, "{}:{:02}:{:02}.{}", hours, mins, seconds, ms); - } else { + } + else { G_FmtTo(buffer, "{:02}:{:02}.{}", mins, seconds, ms); } return buffer; } -team_t StringToTeamNum(const char *in) { +team_t StringToTeamNum(const char* in) { if (!Q_strcasecmp(in, "spectator") || !Q_strcasecmp(in, "s")) { return TEAM_SPECTATOR; - } else if (!Q_strcasecmp(in, "auto") || !Q_strcasecmp(in, "a")) { + } + else if (!Q_strcasecmp(in, "auto") || !Q_strcasecmp(in, "a")) { return PickTeam(-1); - } else if (Teams()) { + } + else if (Teams()) { if (!Q_strcasecmp(in, "blue") || !Q_strcasecmp(in, "b")) return TEAM_BLUE; else if (!Q_strcasecmp(in, "red") || !Q_strcasecmp(in, "r")) return TEAM_RED; - } else { + } + else { if (!Q_strcasecmp(in, "free") || !Q_strcasecmp(in, "f")) return TEAM_FREE; } @@ -1001,7 +1020,7 @@ bool IsScoringDisabled() { return false; } -gametype_t GT_IndexFromString(const char *in) { +gametype_t GT_IndexFromString(const char* in) { for (size_t i = 0; i < gametype_t::GT_NUM_GAMETYPES; i++) { if (!Q_strcasecmp(in, gt_short_name[i])) return (gametype_t)i; @@ -1023,7 +1042,7 @@ void BroadcastReadyReminderMessage() { } } -void TeleportPlayerToRandomSpawnPoint(gentity_t *ent, bool fx) { +void TeleportPlayerToRandomSpawnPoint(gentity_t* ent, bool fx) { bool valid_spawn = false; vec3_t spawn_origin, spawn_angles; bool is_landmark = false; @@ -1050,12 +1069,12 @@ ClientEntFromString Resolve a client entity from a name or validated numeric identifier string. ============= */ -gentity_t *ClientEntFromString(const char *in) { +gentity_t* ClientEntFromString(const char* in) { for (auto ec : active_clients()) if (!strcmp(in, ec->client->resp.netname)) return ec; - char *end = nullptr; + char* end = nullptr; errno = 0; const unsigned long num = strtoul(in, &end, 10); if (errno == ERANGE || !end || *end != '\0') @@ -1071,7 +1090,7 @@ gentity_t *ClientEntFromString(const char *in) { RS_IndexFromString ================= */ -ruleset_t RS_IndexFromString(const char *in) { +ruleset_t RS_IndexFromString(const char* in) { for (size_t i = 1; i < (int)RS_NUM_RULESETS; i++) { if (!strcmp(in, rs_short_name[i])) return (ruleset_t)i; @@ -1081,13 +1100,14 @@ ruleset_t RS_IndexFromString(const char *in) { return ruleset_t::RS_NONE; } -void TeleporterVelocity(gentity_t *ent, gvec3_t angles) { +void TeleporterVelocity(gentity_t* ent, gvec3_t angles) { if (g_teleporter_freeze->integer) { // clear the velocity and hold them in place briefly ent->velocity = {}; ent->client->ps.pmove.pm_time = 160; // hold time ent->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; - } else { + } + else { // preserve velocity and 'spit' them out of destination float len = ent->velocity.length(); @@ -1097,7 +1117,7 @@ void TeleporterVelocity(gentity_t *ent, gvec3_t angles) { } } -static bool MS_Validation(gclient_t *cl, mstats_t index) { +static bool MS_Validation(gclient_t* cl, mstats_t index) { if (!cl) return false; @@ -1115,21 +1135,21 @@ static bool MS_Validation(gclient_t *cl, mstats_t index) { return true; } -int MS_Value(gclient_t *cl, mstats_t index) { +int MS_Value(gclient_t* cl, mstats_t index) { if (!MS_Validation(cl, index)) return 0; return cl->resp.mstats[index]; } -void MS_Adjust(gclient_t *cl, mstats_t index, int count) { +void MS_Adjust(gclient_t* cl, mstats_t index, int count) { if (!MS_Validation(cl, index)) return; cl->resp.mstats[index] += count; } -void MS_AdjustDuo(gclient_t *cl, mstats_t index1, mstats_t index2, int count) { +void MS_AdjustDuo(gclient_t* cl, mstats_t index1, mstats_t index2, int count) { if (!MS_Validation(cl, index1)) return; @@ -1137,7 +1157,7 @@ void MS_AdjustDuo(gclient_t *cl, mstats_t index1, mstats_t index2, int count) { cl->resp.mstats[index2] += count; } -void MS_Set(gclient_t *cl, mstats_t index, int value) { +void MS_Set(gclient_t* cl, mstats_t index, int value) { if (!MS_Validation(cl, index)) return; @@ -1151,8 +1171,8 @@ stime Return a stable timestamp string for file naming. ============= */ -const char *stime() { - struct tm *ltime; +const char* stime() { + struct tm* ltime; time_t gmtime; static char buffer[32]; @@ -1171,7 +1191,7 @@ const char *stime() { return buffer; } -void AnnouncerSound(gentity_t *ent, const char *announcer_sound, const char *backup_sound, bool use_backup) { +void AnnouncerSound(gentity_t* ent, const char* announcer_sound, const char* backup_sound, bool use_backup) { for (auto ec : active_clients()) { if (ent == world || ent == ec || (!ClientIsPlaying(ec->client) && ec->client->follow_target == ent)) { if (ec->client->sess.is_a_bot) @@ -1182,7 +1202,7 @@ void AnnouncerSound(gentity_t *ent, const char *announcer_sound, const char *bac continue; } //gi.local_sound(ec, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex(announcer_sound), 1, ATTN_NONE, 0); - + if (ec->client->sess.pc.use_expanded && announcer_sound) gi.local_sound(ec, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex(G_Fmt("vo_evil/{}.wav", announcer_sound).data()), 1, ATTN_NONE, 0); } @@ -1192,7 +1212,7 @@ void AnnouncerSound(gentity_t *ent, const char *announcer_sound, const char *bac } -void QLSound(gentity_t *ent, const char *ql_sound, const char *backup_sound, bool use_backup) { +void QLSound(gentity_t* ent, const char* ql_sound, const char* backup_sound, bool use_backup) { for (auto ec : active_clients()) { if (ent == world || ent == ec || (!ClientIsPlaying(ec->client) && ec->client->follow_target == ent)) { if (ec->client->sess.is_a_bot) @@ -1203,19 +1223,19 @@ void QLSound(gentity_t *ent, const char *ql_sound, const char *backup_sound, boo continue; } //gi.local_sound(ec, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex(ql_sound), 1, ATTN_NONE, 0); - + if (ec->client->sess.pc.use_expanded && ql_sound) gi.local_sound(ec, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex(G_Fmt("{}.wav", ql_sound).data()), 1, ATTN_NONE, 0); } } } -void G_StuffCmd(gentity_t *e, const char *fmt, ...) { +void G_StuffCmd(gentity_t* e, const char* fmt, ...) { va_list argptr; char text[512]; if (e && !e->client->pers.connected) - gi.Com_ErrorFmt("{}: Bad client %d for '%s'", __FUNCTION__, (int)(e - g_entities - 1), fmt); + gi.Com_ErrorFmt("{}: Bad client {} for '{}'", __FUNCTION__, (int)(e - g_entities - 1), fmt); va_start(argptr, fmt); vsnprintf(text, sizeof(text), fmt, argptr); From ebdf9cb2f654881ba86f91c90eeb552e9df08686 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 10:33:19 +0000 Subject: [PATCH 066/142] Fix save data duplicate message formatting --- src/g_save.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index cb425f6..841d555 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -93,10 +93,12 @@ void InitSave() { const save_data_list_t *existing = existing_name->second; if (!deathmatch->integer) { + const void *existing_ptr = reinterpret_cast(existing); + if (g_strict_saves->integer) - gi.Com_ErrorFmt("duplicate save type name {} (tag {}) already linked to pointer {}, cannot add pointer {}", link->name, (int32_t)link->tag, existing, link_ptr); + gi.Com_ErrorFmt("duplicate save type name {} (tag {}) already linked to pointer {}, cannot add pointer {}", link->name, (int32_t)link->tag, existing_ptr, link_ptr); else - gi.Com_PrintFmt("duplicate save type name {} (tag {}) already linked to pointer {}, cannot add pointer {}", link->name, (int32_t)link->tag, existing, link_ptr); + gi.Com_PrintFmt("duplicate save type name {} (tag {}) already linked to pointer {}, cannot add pointer {}", link->name, (int32_t)link->tag, existing_ptr, link_ptr); } continue; From 30d00da8f6ed924f3a36f74b5507f1d4757ccc65 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:30:40 +0000 Subject: [PATCH 067/142] Handle empty target selection choices --- src/g_utils_target_selection.h | 6 ++++++ src/tests/g_utils_target_selection_test.cpp | 3 +++ 2 files changed, 9 insertions(+) diff --git a/src/g_utils_target_selection.h b/src/g_utils_target_selection.h index e36b3c8..5bbee58 100644 --- a/src/g_utils_target_selection.h +++ b/src/g_utils_target_selection.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include @@ -16,6 +17,11 @@ Selects a random target from a list using the provided random generator. */ template T *G_SelectRandomTarget(const std::vector &choices, RandomFunc &&random_func) { + if (choices.empty()) { + std::fprintf(stderr, "%s: attempted random selection with no available targets.\n", __FUNCTION__); + return nullptr; + } + assert(!choices.empty()); return choices[std::forward(random_func)(choices.size())]; } diff --git a/src/tests/g_utils_target_selection_test.cpp b/src/tests/g_utils_target_selection_test.cpp index 09733b1..5b3cc16 100644 --- a/src/tests/g_utils_target_selection_test.cpp +++ b/src/tests/g_utils_target_selection_test.cpp @@ -35,5 +35,8 @@ int main() { } assert(saw_ninth_entry); + + std::vector empty_choices; + assert(G_SelectRandomTarget(empty_choices, cycling_random) == nullptr); return 0; } From dac8802ee164d0904efa0026b99d7b798ce966ac Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:32:00 +0000 Subject: [PATCH 068/142] Refactor AI_GetSightClient allocation --- src/g_ai.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/g_ai.cpp b/src/g_ai.cpp index bab350a..a346b5b 100644 --- a/src/g_ai.cpp +++ b/src/g_ai.cpp @@ -3,6 +3,7 @@ // g_ai.c #include "g_local.h" +#include bool FindTarget(gentity_t *self); bool ai_checkattack(gentity_t *self, float dist); @@ -28,8 +29,8 @@ gentity_t *AI_GetSightClient(gentity_t *self) { if (level.intermission_time) return nullptr; - gentity_t **visible_players = (gentity_t **)alloca(sizeof(gentity_t *) * game.maxclients); - size_t num_visible = 0; + std::vector visible_players; + visible_players.reserve(game.maxclients); for (auto player : active_clients()) { if (player->health <= 0 || player->deadflag || !player->solid) @@ -43,13 +44,13 @@ gentity_t *AI_GetSightClient(gentity_t *self) { continue; } - visible_players[num_visible++] = player; // got one + visible_players.push_back(player); // got one } - if (!num_visible) + if (visible_players.empty()) return nullptr; - return visible_players[irandom(num_visible)]; + return visible_players[irandom(visible_players.size())]; } //============================================================================ From dc7f696eb92481fe9e5e7a2c9c45f4f0997e70e2 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:32:33 +0000 Subject: [PATCH 069/142] Refactor killtarget iteration safety --- src/g_utils.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 47e1638..d3ca75a 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -250,23 +250,24 @@ void G_UseTargets(gentity_t* ent, gentity_t* activator) { // kill killtargets // if (ent->killtarget) { - t = nullptr; - while ((t = G_FindByString<&gentity_t::targetname>(t, ent->killtarget))) { - if (t->teammaster) { + for (gentity_t* cursor = G_FindByString<&gentity_t::targetname>(nullptr, ent->killtarget); cursor;) { + gentity_t* next = G_FindByString<&gentity_t::targetname>(cursor, ent->killtarget); + + if (cursor->teammaster) { // if this entity is part of a chain, cleanly remove it - if (t->flags & FL_TEAMSLAVE) { - for (gentity_t* master = t->teammaster; master; master = master->teamchain) { - if (master->teamchain == t) { - master->teamchain = t->teamchain; + if (cursor->flags & FL_TEAMSLAVE) { + for (gentity_t* master = cursor->teammaster; master; master = master->teamchain) { + if (master->teamchain == cursor) { + master->teamchain = cursor->teamchain; break; } } } // [Paril-KEX] remove teammaster too - else if (t->flags & FL_TEAMMASTER) { - t->teammaster->flags &= ~FL_TEAMMASTER; + else if (cursor->flags & FL_TEAMMASTER) { + cursor->teammaster->flags &= ~FL_TEAMMASTER; - gentity_t* new_master = t->teammaster->teamchain; + gentity_t* new_master = cursor->teammaster->teamchain; if (new_master) { new_master->flags |= FL_TEAMMASTER; @@ -279,20 +280,21 @@ void G_UseTargets(gentity_t* ent, gentity_t* activator) { } // [Paril-KEX] if we killtarget a monster, clean up properly - if (t->svflags & SVF_MONSTER) { - if (!t->deadflag && !(t->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !(t->spawnflags & SPAWNFLAG_MONSTER_DEAD)) - G_MonsterKilled(t); + if (cursor->svflags & SVF_MONSTER) { + if (!cursor->deadflag && !(cursor->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !(cursor->spawnflags & SPAWNFLAG_MONSTER_DEAD)) + G_MonsterKilled(cursor); } - G_FreeEntity(t); + G_FreeEntity(cursor); if (!ent->inuse) { gi.Com_PrintFmt("{}: Entity was removed while using killtargets.\n", __FUNCTION__); return; } + + cursor = next; } } - // // fire targets // From e257ecb03f07296924b49e0448c5065c9ad89991 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:33:05 +0000 Subject: [PATCH 070/142] Copy delayed target strings --- src/g_utils.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 47e1638..52ff945 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -109,8 +109,25 @@ gentity_t* G_PickTarget(const char* targetname) { }); } +/* +============= +Think_Delay + +Executes delayed target use and frees duplicated strings. +============= +*/ static THINK(Think_Delay) (gentity_t* ent) -> void { G_UseTargets(ent, ent->activator); + + if (ent->message) + gi.TagFree((void*)ent->message); + + if (ent->target) + gi.TagFree((void*)ent->target); + + if (ent->killtarget) + gi.TagFree((void*)ent->killtarget); + G_FreeEntity(ent); } @@ -235,9 +252,9 @@ void G_UseTargets(gentity_t* ent, gentity_t* activator) { t->activator = activator; if (!activator) gi.Com_PrintFmt("{}: {} with no activator.\n", __FUNCTION__, *t); - t->message = ent->message; - t->target = ent->target; - t->killtarget = ent->killtarget; + t->message = G_CopyString(ent->message, TAG_LEVEL); + t->target = G_CopyString(ent->target, TAG_LEVEL); + t->killtarget = G_CopyString(ent->killtarget, TAG_LEVEL); return; } From 9b3d0d5fb3761b8de614a44ed1889096373f5f8b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:39:25 +0000 Subject: [PATCH 071/142] Handle missing save link asserts --- src/g_save.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index 841d555..444a887 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -139,13 +139,19 @@ const save_data_list_t *save_data_list_t::fetch(const void *ptr, save_data_tag_t if (link != list_from_ptr_hash.end() && link->second->tag == tag) return link->second; - // [0] is just to silence warning - assert(false || "invalid save pointer; break here to find which pointer it is"[0]); + const char *tag_name = nullptr; + + for (const auto &entry : list_hash) { + if (entry.second && entry.second->tag == tag) { + tag_name = entry.second->name; + break; + } + } if (g_strict_saves->integer) - gi.Com_ErrorFmt("value pointer {} was not linked to save tag {}\n", ptr, (int32_t)tag); + gi.Com_ErrorFmt("value pointer {} was not linked to save tag {} ({})\n", ptr, (int32_t)tag, tag_name ? tag_name : ""); else - gi.Com_PrintFmt("value pointer {} was not linked to save tag {}\n", ptr, (int32_t)tag); + gi.Com_PrintFmt("value pointer {} was not linked to save tag {} ({})\n", ptr, (int32_t)tag, tag_name ? tag_name : ""); static save_data_list_t invalid_save_data("", SAVE_DATA_MMOVE, nullptr, false, false); @@ -2551,4 +2557,4 @@ bool CanSave() { /*static*/ template<> cached_soundindex *cached_soundindex::head = nullptr; /*static*/ template<> cached_modelindex *cached_modelindex::head = nullptr; -/*static*/ template<> cached_imageindex *cached_imageindex::head = nullptr; \ No newline at end of file +/*static*/ template<> cached_imageindex *cached_imageindex::head = nullptr; From 90cb640d4adf5bfca04594ee3cf3be3c81b711ab Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:39:45 +0000 Subject: [PATCH 072/142] Add guard for notify expiration --- src/cg_screen.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/cg_screen.cpp b/src/cg_screen.cpp index b3445ba..ef8e74c 100644 --- a/src/cg_screen.cpp +++ b/src/cg_screen.cpp @@ -112,14 +112,29 @@ void CG_ClearNotify(int32_t isplit) { // if the top one is expired, cycle the ones ahead backwards (since // the times are always increasing) +/* +============= +CG_Notify_CheckExpire + +Expire stale notify entries with bounded swapping to prevent corrupted times +from triggering excessive iterations. +============= +*/ static void CG_Notify_CheckExpire(hud_data_t &data) { - while (data.notify[0].is_active && data.notify[0].time < cgi.CL_ClientTime()) { + size_t iterations = 0; + + while (data.notify[0].is_active && data.notify[0].time < cgi.CL_ClientTime() && iterations < MAX_NOTIFY) { data.notify[0].is_active = false; for (size_t i = 1; i < MAX_NOTIFY; i++) if (data.notify[i].is_active) std::swap(data.notify[i], data.notify[i - 1]); + + iterations++; } + + if (iterations >= MAX_NOTIFY && data.notify[0].is_active && data.notify[0].time < cgi.CL_ClientTime()) + data.notify[0].is_active = false; } // add notify to list From a3ce0a8cec8d69b661a67ebe5fa88eda6828be93 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Thu, 20 Nov 2025 23:40:10 +0000 Subject: [PATCH 073/142] Refine packet filter byte packing --- src/g_svcmds.cpp | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/g_svcmds.cpp b/src/g_svcmds.cpp index 4296fd6..faddc5b 100644 --- a/src/g_svcmds.cpp +++ b/src/g_svcmds.cpp @@ -2,6 +2,22 @@ // Licensed under the GNU General Public License 2.0. #include "g_local.h" +#include + +/* +============= +PackBytesToUint32 + +Assembles four bytes into a uint32_t using little-endian order to maintain +consistent packet filter behavior across platforms. +============= +*/ +static uint32_t PackBytesToUint32(const byte bytes[4]) { + return (static_cast(bytes[0])) | + (static_cast(bytes[1]) << 8) | + (static_cast(bytes[2]) << 16) | + (static_cast(bytes[3]) << 24); +} static void Svcmd_Test_f() { gi.LocClient_Print(nullptr, PRINT_HIGH, "Svcmd_Test_f()\n"); @@ -44,8 +60,8 @@ only allows players from your local network. */ struct ipfilter_t { - unsigned mask; - unsigned compare; + uint32_t mask; + uint32_t compare; }; constexpr size_t MAX_IPFILTERS = 1024; @@ -60,7 +76,7 @@ StringToFilter */ static bool StringToFilter(const char *s, ipfilter_t *f) { char num[128]; - int i, j; + int i, j; byte b[4]; byte m[4]; @@ -89,8 +105,8 @@ static bool StringToFilter(const char *s, ipfilter_t *f) { s++; } - f->mask = *(unsigned *)m; - f->compare = *(unsigned *)b; + f->mask = PackBytesToUint32(m); + f->compare = PackBytesToUint32(b); return true; } @@ -101,9 +117,9 @@ G_FilterPacket ================= */ bool G_FilterPacket(const char *from) { - int i; - unsigned in; - byte m[4]; + int i; + uint32_t in; + byte m[4]; const char *p; i = 0; @@ -120,7 +136,7 @@ bool G_FilterPacket(const char *from) { p++; } - in = *(unsigned *)m; + in = PackBytesToUint32(m); for (i = 0; i < numipfilters; i++) if ((in & ipfilters[i].mask) == ipfilters[i].compare) From 8fa7559e4d2d03724ee98a3fffad52b2d674ed41 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Fri, 21 Nov 2025 11:38:08 +0000 Subject: [PATCH 074/142] Define save data constructor and add HUD victor build --- src/g_save.cpp | 8 ++++++++ src/game.vcxproj | 2 ++ src/game.vcxproj.filters | 2 ++ 3 files changed, 12 insertions(+) diff --git a/src/g_save.cpp b/src/g_save.cpp index 444a887..cc9270c 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -113,6 +113,14 @@ void InitSave() { } // initializer for save data /* +============= +save_data_list_t::save_data_list_t + +Registers a save data entry and optionally links it into the global list used for lookups. +============= +*/ +save_data_list_t::save_data_list_t(const char *name_in, save_data_tag_t tag_in, const void *ptr_in, bool link, bool valid_in) : + name(name_in), tag(tag_in), ptr(ptr_in), next(nullptr), diff --git a/src/game.vcxproj b/src/game.vcxproj index e08d35f..f7bc1ee 100644 --- a/src/game.vcxproj +++ b/src/game.vcxproj @@ -144,6 +144,7 @@ + @@ -214,6 +215,7 @@ + diff --git a/src/game.vcxproj.filters b/src/game.vcxproj.filters index c2dc86b..839e7a8 100644 --- a/src/game.vcxproj.filters +++ b/src/game.vcxproj.filters @@ -126,6 +126,7 @@ monsters + @@ -152,6 +153,7 @@ + From 456f01cc6dc713772e20a0a654fb15f912a8af71 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Fri, 21 Nov 2025 12:06:11 +0000 Subject: [PATCH 075/142] Create muffmode.png --- assets/img/muffmode.png | Bin 0 -> 675402 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/img/muffmode.png diff --git a/assets/img/muffmode.png b/assets/img/muffmode.png new file mode 100644 index 0000000000000000000000000000000000000000..aad564844182f7c454936538b2e7739789d605a3 GIT binary patch literal 675402 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 z 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

7dn% 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

@v7 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

@^yp?tS~a<4<}AEju+r{c1NcvwfE|CYu>!T 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

gDW1ADpy=dc}j4Sa#Bu(q{=qfsW>B0 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⁣XU?Y^B+FS3zqZoE(z&gg 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%0AS_}c29`g5a~DcvRpoMHC6VOP3xWDDRU49Y1F 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 Date: Fri, 21 Nov 2025 12:10:15 +0000 Subject: [PATCH 076/142] Revise README formatting and content Updated the README to improve formatting and clarity. --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3f22a02..59fd19c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,20 @@ -# Welcome to Muff Mode BETA! +

    + Logo +

    Muff Mode BETA

    +
    +

    +Muff Mode is a server-side mod for QUAKE II Remastered [QUAKE II Remastered](https://github.com/id-Software/quake2-rerelease-dll) providing an enhanced multiplayer experience. +

    -## What is Muff Mode? -Muff Mode is a server-side mod for [QUAKE II Remastered](https://github.com/id-Software/quake2-rerelease-dll) providing overall enhances functionality and refinements. +--- -### It is for Server Hosts +### It's for Server Hosts With a focus on multiplayer, it provides refined match handling and an extensive set of new capabilities for server owners to configure the game in a host of new ways. -### It is for Level Designers +### It's for Level Designers New creative possibilities are unlocked for level designers, with an array of new map entities and keys and a range of added gametypes to design for. -### It is for the Players(tm) +### It's for the Playersâ„¢ Enhanced HUD info and a number of changable settings. ## Installation From 998a223bb848ecec76684810bc011ec01b5617e7 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Fri, 21 Nov 2025 12:12:49 +0000 Subject: [PATCH 077/142] Update project name from 'Remastered' to 'Rerelease' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 59fd19c..8bfea7b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

    Muff Mode BETA

    -Muff Mode is a server-side mod for QUAKE II Remastered [QUAKE II Remastered](https://github.com/id-Software/quake2-rerelease-dll) providing an enhanced multiplayer experience. +Muff Mode is a server-side mod for QUAKE II Rerelease providing an enhanced multiplayer experience.

    --- From 7b19e7599e4bf97d9de474cf03d9f99e30b57893 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Fri, 21 Nov 2025 12:44:00 +0000 Subject: [PATCH 078/142] Refresh README styling --- README.md | 583 +++++++++++++++--------------------------------------- 1 file changed, 155 insertions(+), 428 deletions(-) diff --git a/README.md b/README.md index 8bfea7b..ab26c30 100644 --- a/README.md +++ b/README.md @@ -1,464 +1,191 @@ -
    - Logo -

    Muff Mode BETA

    -
    -

    -Muff Mode is a server-side mod for QUAKE II Rerelease providing an enhanced multiplayer experience. -

    + + + + +
    + Muff Mode Logo +

    Muff Mode BETA

    +

    + Muff Mode is a server-side mod for QUAKE II Rerelease + providing an enhanced multiplayer experience. +

    +
    --- -### It's for Server Hosts -With a focus on multiplayer, it provides refined match handling and an extensive set of new capabilities for server owners to configure the game in a host of new ways. +## Overview +- Purpose-built for multiplayer server hosts, level designers, and players who want modern conveniences. +- Bundles refined match handling, new game entities and gametypes, and quality-of-life tweaks. +- Ships with configuration, bot, and map overrides ready to go out of the box. -### It's for Level Designers -New creative possibilities are unlocked for level designers, with an array of new map entities and keys and a range of added gametypes to design for. - -### It's for the Players™ -Enhanced HUD info and a number of changable settings. +## Audience Snapshot +| Server Hosts | Level Designers | Players™ | +| --- | --- | --- | +| Refined match handling, warmups, countdowns, and flexible admin controls. | Expanded map entities/keys and added gametypes to design around. | Enhanced HUD info and configurable settings. | ## Installation -1. Locate your installation. For Steam, this is normally "C:\Program Files (x86)\Steam\steamapps\common\Quake 2\rerelease". -2. Back up your baseq2/game_x64.dll. +1. Locate your installation. For Steam, this is normally `C:\Program Files (x86)\Steam\steamapps\common\Quake 2\rerelease`. +2. Back up your `baseq2/game_x64.dll`. 3. Download the latest [Muff Mode release](https://github.com/themuffinator/muffmode/releases/latest). -4. Extract the entire zip to your "Quake 2" folder (not rerelease), allow file replacements (unless you already have Muff Mode, this should only replace the .dll). +4. Extract the entire zip to your `Quake 2` folder (not `rerelease`); allow file replacements (unless you already have Muff Mode, this should only replace the .dll). 5. Load the game up as normal. A range of configs can be executed to apply settings once a game has been set up. -6. Once a lobby has been set up, you can execute the included server config via ``exec muff-sv.cfg``. +6. Once a lobby has been set up, execute the included server config via `exec muff-sv.cfg`. ## What's in the Bag? -Muff Mode includes the game logic, a server config, bot files and some map entity overrides all straight out of the bag! +Muff Mode includes the game logic, a server config, bot files, and map entity overrides all straight out of the bag. ## Feature Overview -- Refined HUD and scoreboard, more purpose-built for what information is needed and some extra features: - * Frag messages (also showing position in match) - * Dynamic miniscores with scorelimit - * Timer and match state - * Help texts - * Message of the Day - * Mini scoreboard -- A game menu for joining a match, changing for or voting on settings and viewing mod and server info. -- A whole host of controls for admins, voting and more. -- Refined match handling with conditional progression, including: warmups, readying, countdowns, post-match delays, sudden death, overtime and more. -- Enhanced teamplay with team auto-balancing, forced balancing rules, improved team handling, communicating joined team to players, major item pickup and weapon drop POI's, and friendly fire warnings. -- Extensive controls over specific map item spawns and entity string overrides. -- EyeCam spectating, smooth and with aim prediction (mostly!) -- MyMap map queing system inspired by tastyspleen.net. -- A number of bug fixes, minor refinements, balance tweaks and many new server settings added. -- Muff Maps: official maps under development included utilizing Muff Mode's enhanced capabilities -- Many more features, see release changelogs for more info! +- **HUD & Scoreboard:** Frag messages (with position), dynamic miniscores with scorelimit, timers, match state, help texts, message of the day, mini scoreboard. +- **Game Menu:** Join matches, change or vote on settings, and view mod/server info. +- **Admin & Voting Controls:** Broad controls for admins plus improved voting workflows. +- **Match Handling:** Conditional progression with warmups, readying, countdowns, post-match delays, sudden death, overtime, and more. +- **Teamplay Enhancements:** Auto-balancing, forced balancing rules, improved team handling, team join communication, POIs for major items and weapon drops, friendly-fire warnings. +- **Map & Entity Control:** Extensive control over map item spawns and entity string overrides. +- **Spectating:** EyeCam spectating with smooth movement and aim prediction (mostly!). +- **Queues & Content:** MyMap map queuing inspired by tastyspleen.net and official **Muff Maps** leveraging enhanced capabilities. +- **Fixes & Tweaks:** Bug fixes, balance updates, and a variety of new server settings. ### Muff Maps -- Almost Lost [ALpha v1] (mm-almostlost-a1) - Left out from Quake III: Arena's release, this level was eventually finished off and officially released as a seperate map as pro-q3tourney7. Revised in Quake Live, this map is streamlined for fast-paced FFAs and Duels. -- Arena of Death [Alpha v3] (mm-arena-a3) - A small and simple gem from Quake III: Arena. -- Hidden Fortress [Alpha v4] (mm-fortress-a4) - A small-to-medium-sized level connected via two teleporters. Originally created by Raster Productions for Quake III for Dreamcast, this offers the revised layout found in Quake Live. -- Longest Yard [Beta v2] (mm-longestyard-b2) - The quintessential space map from Quake III: Arena. -- Proving Grounds [Alpha v4] (mm-proving-a4) - A small Duel map from Quake III: Arena. -- Vertical Vengeance [Alpha v2] (mm-vengeance-a2) - A small Duel map from Quake III: Arena. +- **Almost Lost [Alpha v1] (mm-almostlost-a1):** Left out from Quake III: Arena's release, later finished and released as pro-q3tourney7. Streamlined for fast-paced FFAs and Duels. +- **Arena of Death [Alpha v3] (mm-arena-a3):** A small and simple gem from Quake III: Arena. +- **Hidden Fortress [Alpha v4] (mm-fortress-a4):** Small-to-medium-sized level connected via two teleporters. Originally created by Raster Productions for Quake III for Dreamcast, offering the revised layout found in Quake Live. +- **Longest Yard [Beta v2] (mm-longestyard-b2):** The quintessential space map from Quake III: Arena. +- **Proving Grounds [Alpha v4] (mm-proving-a4):** A small Duel map from Quake III: Arena. +- **Vertical Vengeance [Alpha v2] (mm-vengeance-a2):** A small Duel map from Quake III: Arena. ### New Gametypes -- Horde: Battle waves of monsters, stay on top of the scoreboard while defeating up to 16 waves to be victorious! Note: currently does not handle limited lives. -- Duel: Go head-to-head with an opponent. The victor goes on to face their next opponent in the queue. -- Clan Arena: Rocket Arena's famous round-based team elimination mode - no item spawns, no self-damage and a full arsenal of weapons. -- CaptureStrike: A Threewave classic, combines Clan Arena, CTF and Counter Strike. Teams take turns attacking or defending and battle until one team is dead, or the attacking team captures the flag. -- Red Rover: Clan Arena style where teams are changed on death. When a team has been eliminated, the round ends. +- **Horde:** Battle waves of monsters; stay on top of the scoreboard while defeating up to 16 waves to be victorious! Note: currently does not handle limited lives. +- **Duel:** Go head-to-head with an opponent; the victor faces the next opponent in the queue. +- **Clan Arena:** Rocket Arena's famous round-based team elimination mode—no item spawns, no self-damage, and a full arsenal of weapons. +- **CaptureStrike:** A Threewave classic combining Clan Arena, CTF, and Counter Strike. Teams alternate attacking/defending and battle until one team is dead or the attackers capture the flag. +- **Red Rover:** Clan Arena style where teams change on death. When a team is eliminated, the round ends. ### New Game Modifications -- Vampiric Damage: Gain health by inflicting damage on your foes! No health pickups and a draining health value means the pressure is on! -- Nade Fest: Grenade-only mode. -- Weapons Frenzy: Intensified combat! Faster rates of fire, faster rockets, regenerating ammo, faster weapon switching. -- Quad Hog: Find the Quad Damage to become the Quad Hog! +- **Vampiric Damage:** Gain health by inflicting damage. No health pickups; a draining health value keeps pressure high. +- **Nade Fest:** Grenade-only mode. +- **Weapons Frenzy:** Intensified combat with faster fire rates, faster rockets, regenerating ammo, and faster weapon switching. +- **Quad Hog:** Find the Quad Damage to become the Quad Hog! ### Deathmatch Refinements -- Intermission pre-delay: a one second intermission pre-delay means you can see your winning frag or capture before final scores (no damage is taken or additional scoring during this delay). -- Minimum respawn delay: a short respawn delay helps avoid accidental respawning and creates a smoother transition. +- Intermission pre-delay: a one-second intermission pre-delay shows your final frag or capture before scores tally (no damage or additional scoring during this delay). +- Minimum respawn delay: a short delay helps avoid accidental respawns and creates a smoother transition. - Kill beeps and frag messages highlight your frags and rank. - + ### Offhand Hook -- Added 'hook' and 'unhook' commands to use off-hand hook. Use `g_grapple_offhand 1` to enable this. -- Players can use ``alias +hook hook; alias -hook unhook; bind mouse2 +hook`` to use it as a button command +- Added `hook` and `unhook` commands to use off-hand hook. Use `g_grapple_offhand 1` to enable. +- Players can use `alias +hook hook; alias -hook unhook; bind mouse2 +hook` to use it as a button command. ### Gameplay Tweaks and Fixes - - Instagib and Nade Fest now give players regeneration to recover from environmental damage, falling damage etc. - - Quad and Protection player color shells don't change depending on team, avoids confusion - - func_rotating: Rotating map entities now explode non-player ents such as dropped items, practically this means no more blocked rotator in dm5. - - Player Feedback: - * Added Fragging Spree award - broadcasted message "x is on a fragging spree with x frags" per every 10 frags achieved without dying or killing a team mate - - Techs: fixed not being able to pick up your dropped tech - - Blaster and Grapple now both droppable and can spawn in world - - Current weapon is now droppable - - Smart weapon auto-switch: now switches to SSG from SG, CG from MG, never auto-switches to chainfist. - - Instant gametype changing (eg: from FFA to TDM) - - DuelFire Damage has been changed to Haste: 50% faster movement, 50% faster weapon rate of fire. - - Many more! +- Instagib and Nade Fest now give players regeneration to recover from environmental damage, falling damage, etc. +- Quad and Protection player color shells no longer change depending on team, avoiding confusion. +- `func_rotating`: Rotating map entities now explode non-player entities such as dropped items (no more blocked rotator in dm5). +- Player Feedback: Added Fragging Spree award—broadcasted message "x is on a fragging spree with x frags" per every 10 frags achieved without dying or killing a team mate. +- Techs: fixed not being able to pick up your dropped tech. +- Blaster and Grapple are now droppable and can spawn in world. +- Current weapon is now droppable. +- Smart weapon auto-switch now moves to SSG from SG, CG from MG, and never auto-switches to chainfist. +- Instant gametype changing (e.g., from FFA to TDM). +- DuelFire Damage has been changed to Haste: 50% faster movement, 50% faster weapon rate of fire. +- Many more! ## Rulesets Alter the gameplay balance by changing the ruleset. -### Quake II Rerelease (g_ruleset 1) +### Quake II Rerelease (`g_ruleset 1`) Everything remains as is. -### Muff Mode (g_ruleset 2) -This ruleset aims to tackle a few significant imbalances in the original game: - - **Plasma Beam** DM damage reduced from 15 to 10, maximum range limited to 768 units (same as LG in Q3) - - **Railgun**: restored to 150 damage in campaigns, rail knockback is now equal to damage*2 (no difference in DM). - - **Slugs/Railgun**: reduced slugs quantity from 10 to 5, should force more dynamic and challenging gameplay instead of an instagib approach to some matches. - - **Rockets**: removed randomised direct rocket damage value (rand 100-120), now a consistent 120. - - **Invulnerability** powerup has been replaced by Protection - player receives no splash damage, full protection from slime damage, third protection from lava, half direct damage after armor protection. - - **Adrenaline**: item now also increases max health by 5 during deathmatch. - - **Rebreather**: increased holding time from 30 to 45 seconds. - - **Auto Doc**: regen time increased from 500 ms to 1 sec, only regens either health or armor at a time - - **Power Armor**: CTF's 1 damage per cell now applies across deathmatch (originally 2 damage per cell in all DM bar CTF) -- this means same protection but consumes roughly twice the cells. - - Powerup spawn rules: 120 sec respawn default, 30-45 (randomised) initial spawn delay, global spawn and pickup sounds, spawn and pickup messages. +### Muff Mode (`g_ruleset 2`) +This ruleset tackles several imbalances in the original game: +- **Plasma Beam** DM damage reduced from 15 to 10; maximum range limited to 768 units (same as LG in Q3). +- **Railgun:** restored to 150 damage in campaigns; rail knockback equals damage*2 (no difference in DM). +- **Slugs/Railgun:** reduced slug quantity from 10 to 5 to encourage dynamic gameplay. +- **Rockets:** removed randomized direct rocket damage (rand 100-120); now a consistent 120. +- **Invulnerability** powerup replaced by Protection—no splash damage, full protection from slime damage, one-third protection from lava, half direct damage after armor protection. +- **Adrenaline:** item now also increases max health by 5 during deathmatch. +- **Rebreather:** increased holding time from 30 to 45 seconds. +- **Auto Doc:** regen time increased from 500 ms to 1 sec; only regens either health or armor at a time. +- **Power Armor:** CTF's 1 damage per cell now applies across deathmatch (originally 2 damage per cell in all DM bar CTF)—same protection but roughly twice the cell consumption. +- Powerup spawn rules: 120 sec respawn default, 30–45 (randomized) initial spawn delay, global spawn and pickup sounds, spawn and pickup messages. + +### Quake III Arena style (`g_ruleset 3`) +Inspired by Quake III Arena, this ruleset replicates key differences: +- Start with Machinegun and Rip Saw. +- Super Shotgun replaced by Shotgun. +- Weapon stats altered, including projectile velocity, spread, and damage. +- Ammo stats altered; ammo max is 200 for each type. +- Weapon pickup rule: +1 ammo if weapon is already held. +- Armor system: no tiers, +5 shard value, armor always provides 66% protection. +- Health and armor count down to max health. +- Spawning health bonus of 25. +- Removed Mega timer rule; Mega Health respawns after 60 seconds. +- **Invulnerability** powerup replaced by Protection—no splash damage, full protection from slime damage, one-third protection from lava, half direct damage after armor protection. +- Powerup spawn rules: 120 sec respawn default, 30–45 (randomized) initial spawn delay, global spawn and pickup sounds. -### Quake III Arena style (g_ruleset 3) -Inspired by Quake III Arena, this ruleset aims to replicate some of the differences: - - Start with Machinegun and Rip Saw - - Super Shotgun replaced by Shotgun - - Weapon stats altered, including projectile velocity, spread and damage - - Ammo stats altered, ammo max is 200 for each type. - - Weapon pickup rule: +1 ammo if weapon is already held. - - Armor system: no tiers, +5 shard value, armor always provides 66% protection - - Health and armor counts down to max health - - Spawning health bonus of 25. - - Removed Mega timer rule, Mega Health respawns after 60 seconds - - **Invulnerability** powerup has been replaced by Protection - player receives no splash damage, full protection from slime damage, third protection from lava, half direct damage after armor protection. - - Powerup spawn rules: 120 sec respawn default, 30-45 (randomised) initial spawn delay, global spawn and pickup sounds. - ## Commands and Variables ### Admin Commands -Use **[command] [arg]** for the below listed admin commands: - - **startmatch**: force the match to start, requires warmup. - - **endmatch**: force the match to end, requires a match in progress. - - **resetmatch**: force the match to reset to warmup, requires a match in progress. - - **lockteam [red/blue]**: locks a team from being joined - - **unlockteam [red/blue]**: unlocks a locked team so players can join - - **setteam [clientnum/name]**: forces a team change for a client - - **shuffle**: shuffles and balances the teams, resets the match. Requires a team gametype. - - **balance**: balances the teams without a shuffle, this switches last joined players from stacked team. Requires a team gametype. - - **vote [yes/no]**: passes or fails a voting in progress - - **spawn [entity_name] [spawn_args]**: spawns an entity, works the same as normal spawn server command but allows admins to do this without cheats enabled - - **map**: changes the level to the specified map, map needs to be a part of the map list. - - **nextmap**: forces level change to the next map. - - **map_restart**: restarts current level and session, applies latches cvar changes - - **gametype [gametype_name]**: changes gametype to selected option, then resets the level - - **ruleset **: changes gameplay style - - **readyall**: force all players to ready status (during readying warmup status) - - **unreadyall**: force all players to NOT ready status (during readying warmup status) - -### Client Commands - Player Configuration -Use **[command] [arg]** for the below listed client commands: - - **announcer**: toggles support of QL match announcer events (uses vo_evil set, needs converting to 22KHz PCM WAV) - - **fm**: toggle frag messages - - **help**: toggle help text drawing - - **id**: toggle crosshair ID drawing - - **kb**: toggle kill beeps - - **timer**: toggle match timer drawing - -### Client Commands - Gameplay - - **hook/unhook**: hook/unhook off-hand grapple - - **followkiller** : auto-follow killers when spectating (disabled by default) - - **followleader** : when spectating, auto-follows leading player - - **followpowerup** : auto-follows player picking up powerups when spectating (disabled by default) - - **forfeit**: forfeits a match (currently only in duels, requires g_allow_forfeit 1). - - **ready/notready**: sets ready status. - - **readyup**: toggles ready status. - - **callvote/cv**: calls a vote (use vote commands). - - **vote [yes/no]**: vote or veto a callvote. - - **maplist**: show server map list. - - **motd"": print the message of the day. - - **mymap**: add a map to the queue, must be a valid map from map list. - - **team [arg]**: selects a team, args: - - **blue/b**: select blue team - - **red/r**: select red team - - **auto/a**: auto-select team - - **free/f**: join free team (non-team games) - - **spectator/s**: spectate - - **time-in** : cuts a time out short - - **time-out** : call a time out, only 1 allowed per player and lasts for value set by g_dm_timeout_length (in seconds). **g_dm_timeout_length 0** disables time outs - - **follow [clientname/clientnum]**: follow a specific player. +Use **[command] [arg]** for the commands below: +- **startmatch**: force the match to start; requires warmup. +- **endmatch**: force the match to end; requires a match in progress. +- **resetmatch**: reset to warmup; requires a match in progress. +- **lockteam [red/blue]**: lock a team from being joined. +- **unlockteam [red/blue]**: unlock a locked team. +- **setteam [clientnum/name]**: force a team change for a client. +- **shuffle**: shuffle and balance teams, then reset the match (requires a team gametype). +- **balance**: balance teams without a shuffle; switches last-joined players from stacked team (requires a team gametype). +- **vote [yes/no]**: pass or fail a vote in progress. +- **spawn [entity_name] [spawn_args]**: spawn an entity (same as normal spawn server command) without requiring cheats. +- **map**: change the level to a specified map; map must be in the map list. +- **nextmap**: force level change to the next map. +- **map_restart**: restart current level and session; applies latched cvar changes. +- **gametype [gametype_name]**: change gametype, then reset the level. +- **ruleset **: change gameplay style. +- **readyall**: force all players to ready status (during readying warmup status). +- **unreadyall**: force all players to NOT ready status (during readying warmup status). + +### Client Commands – Player Configuration +Use **[command] [arg]**: +- **announcer**: toggle support of QL match announcer events (uses vo_evil set, needs converting to 22KHz PCM WAV). +- **fm**: toggle frag messages. +- **help**: toggle help text drawing. +- **id**: toggle crosshair ID drawing. +- **kb**: toggle kill beeps. +- **timer**: toggle match timer drawing. + +### Client Commands – Gameplay +- **hook/unhook**: hook/unhook off-hand grapple. +- **followkiller**: auto-follow killers when spectating (disabled by default). +- **followleader**: when spectating, auto-follow leading player. +- **followpowerup**: auto-follow player picking up powerups when spectating (disabled by default). +- **forfeit**: forfeits a match (currently only in duels, requires `g_allow_forfeit 1`). +- **ready/notready**: set ready status. +- **readyup**: toggle ready status. +- **callvote/cv**: call a vote (use vote commands). +- **vote [yes/no]**: vote or veto a callvote. +- **maplist**: show server map list. +- **motd""**: print the message of the day. +- **mymap**: add a map to the queue (must be a valid map from map list). +- **team [arg]**: select a team. Args: + - **blue/b**: select blue team + - **red/r**: select red team + - **auto/a**: auto-select team + - **free/f**: join free team (non-team games) + - **spectator/s**: spectate +- **time-in**: cut a time out short. +- **time-out**: call a time out; only 1 allowed per player and lasts for value set by `g_dm_timeout_length` (in seconds). `g_dm_timeout_length 0` disables time outs. +- **follow [clientname/clientnum]**: follow a specific player. ### Vote Commands -Use **callvote [command] [arg]** for the below listed vote commands: - - **map**: changes the level to the specified map, map needs to be a part of the map list. - - **nextmap**: forces level change to the next map. - - **restart**: force the match to reset to warmup, requires a match in progress. - - **gametype**: changes gametype to the specified type (ffa|duel|tdm|ctf|ca|ft|rr|strike|lms|horde) - - **timelimit**: changes timelimit to the minutes specified. - - **scorelimit**: changes scorelimit to the value specified. - - **shuffle**: shuffles and balances the teams, resets the match. Requires a team gametype. - - **balance**: balances the teams without a shuffle, this switches last joined players from stacked team. Requires a team gametype. - - **unlagged**: enables or disables lag compensation. - - **cointoss**: randomly returns either HEADS or TAILS. - - **random**: randomly returns a number from 2 to argument value, 100 max. - - **ruleset **: changes gameplay style - -### Cvar Changes - - g_dm_spawn_farthest: added an option, valid values are as follows: - - 0: high random (selects random spawn point except the 2 nearest) - - 1: half farthest (selects random spawn point from the furthest 50% of spawn points - - 2: spawn farthest to current position - - g_teamplay_force_join: renamed to g_dm_force_join - - sv_*: all mod-based sv_* cvars renamed g_* - - g_teleporter_nofreeze: renamed to g_teleporter_freeze, values do opposite effect (value of 1 freezes player), default is 0 (no freeze) - - deathmatch: default changed to 1 - -### New Cvars - - **bot_name_prefix**: allows changing bot name prefixes (blank to remove) (default "B|") - - **g_allow_admin**: allows administrative powers (default 1) - - **g_allow_custom_skins**: when set to 0, reverts any custom player models or skins to stock replacements (default: 0) - - **g_allow_forfeit**: Allows a player to forfeit the match, currently only for Duels (default 1) - - **g_allow_kill**: enables use of 'kill' suicide command (default 1) - - **g_allow_mymap**: allow mymap (map queuing function) (default 1) - - **g_allow_spec_vote**: Allows/prohibits voting from spectators. (default 1) - - **g_allow_vote_midgame**: Allows/prohibits voting during a match. (default 0) - - **g_allow_voting**: General control over voting, 0 prohibits any voting. (default 0) - - **g_arena_start_armor**: sets starting armor value in arena modes, range from 1-999, value affects armor tier (default 200) - - **g_arena_start_health**: sets starting health value in arena modes, range from 1-999 (default 200) - - **g_corpse_sink_time**: sets time in seconds for corpses to sink and disappear (default: 60) - - **g_dm_allow_no_humans**: when set to 1, allows matches to start or continue with only bots (default 1) - - **g_dm_do_readyup**: Enforce players to ready up to progress from match warmup stage (requires g_dm_do_warmup 1). (default 0) - - **g_dm_do_warmup**: Allow match warmup stage. (default 1) - - **g_dm_force_join**: replaces g_teamplay_force_join, the menu forces the cvar change so this gets around that, it now applies to regular DM too so the change makes sense. - - **g_dm_holdable_adrenaline** : when set to 1, allows holdable Adrenaline during deathmatch (default 1) - - **g_dm_no_self_damage**: when set to 1, disables any self damage after calculating knockback (default: 0) - - **g_dm_overtime**: Set stoppage time for each overtime session in seconds. Currently only applies to Duels. (default 120) - - **g_dm_powerup_drop**: when set to 1, drops carried powerups upon death (default: 1) - - **g_dm_powerups_minplayers**: Sets minimum current player load to allow powerup pickups, 0 to disable (default 0) - - **g_dm_respawn_delay_min**: the counterpart to g_dm_force_respawn_time, this sets a minimum respawn delay after dying (default: 1) - - **g_dm_respawn_point_min_dist**: sets minimum distance to respawn away from previous spawn point (default: 256, max = 512, 0 = disabled) - - **g_dm_respawn_point_min_dist_debug**: when set to 1, prints avoiding spawn points when g_dm_respawn_point_min_dist is used (default: 0) - - **g_dm_spawnpads**: Controls spawning of deathmatch spawn pads, removes pads when set to 0, 1 only removes in no item game modes, 2 forces pads in all dm matches. (default: 1) - - **g_drop_cmds**: bitflag operator, allows dropping of item types (default 7): - &1: allow dropping CTF flags - &2: allow dropping powerups - &4: allow dropping weapons and ammo - - **g_fast_doors**: When set to 1, doubles the default speed of standard and rotating doors (default 1) - - **g_frag_messages**: draw frag messages (default 1) - - **g_gametype**: cvar sets gametype by index number, this is the current list: - 0: Campaign (not used at present, use deathmatch 0 as usual) - 1. Free for All - 2. Duel - 3. Team Deathmatch - 4. Capture the Flag - 5. Clan Arena - 6. Freeze Tag (WIP) - 7. CaptureStrike - 8. Red Rover - 9. Last Man Standing - 10. Horde - 11. Race (WIP) - - **g_inactivity**: Values above 0 enables an inactivity timer for players, specifying number of seconds since last input to point of flagging the player as inactive. A warning is sent to the player 10 seconds before triggering and once triggered, the player is moved to spectators. Inactive clients are noted as such using the 'players' command. (default: 120) - - **g_instagib_splash**: enables a non-damaging explosion from railgun shots in instagib, allows for rail jumping or knocking foes about (default 0) - - **g_knockback_scale**: scales all knockback resulting from damage received (default 1.0) - - **g_ladder_steps**: Allow ladder step sounds, 1 = only in campaigns, 2 = always on (default 1) - - **g_match_lock**: when set to 1, prohibits joining the match while in progress (default 0) - - **g_motd_filename**: points to filename of message of the day file, reverts to default when blank (default motd.txt) - - **g_mover_speed_scale**: sets speed scaling factor for all movers in maps (doors, rotators, lifts etc.) (default: 1.0f) - - **g_no_powerups**: disable powerup pickups (Quad, Protection, Double, Haste, Invisibility, etc.) - - **g_owner_auto_join**: when set to 0, avoids auto-joining a match as lobby owner (default 1) - - **g_round_countdown**: sets round countdown time (in seconds) in round-based gametypes (default 10) - - **g_ruleset**: gameplay rules (default 2): - 1. Quake II Rerelease - 2. Muff Mode (rebalanced Q2Re) - 3. Quake III Arena style - - **g_showhelp**: when set to 1, prints a quick explanation about game modifications to players. (default: 1) - - **g_starting_armor**: sets starting armor for players on spawn (0-999) (default 0) - - **g_starting_health**: sets starting health for players on spawn (1-999) (default 100) - - **g_teamplay_allow_team_pick**: When set to 0, denies the ability to pick a specific team during teamplay. This changes the join menu accordingly. (default 0) - - **g_teamplay_auto_balance**: Set to 1, enforces team rebalancing during a match. The last joined player(s) of the stacked team switches teams but retain their scores. (default 1) - - **g_teamplay_force_balance**: When set to 1, prohibits joining a team with too many players. (default: 0) - - **g_teamplay_item_drop_notice**: When set to 1, sends team notice of item drops. (default 1) - - **g_teleporter_freeze**: When set to 0, does not freeze player velocity when teleporting. (default: 0) - - **g_vampiric_exp_min**: with vampiric damage enabled, sets expiration minimum health value (default 0) - - **g_vampiric_health_max**: sets maximum health cap from vampiric damage (default 999) - - **g_vampiric_percentile**: set health percentile bonus for vampiric damage (default 0.67f) - - **g_vote_flags**: Bitmask to disable specific vote options. (default 0) - - **g_vote_limit**: Sets maximum number of votes per match per client, 0 for no limit. (default 3) - - **g_warmup_ready_percentage**: in match mode, sets percentile of ready players out of total players required to start the match. Set to 0 to disable readying up. (default: 0.51f) - - **g_weapon_projection**: changes weapon projection offset. 0 = normal, 1 = always force central handedness, 2 = force central view projection. looks strange with view weapons. (default: 0) - - **hostname**: set string for server name, this gets printed at top of game menu for all to see. Limit this to 26 chars max. - - **maxplayers**: Set max number of players in the game (ie: non-spectators), it is capped to maxclients. In team games, team max size will be maxplayers/2 and rounded down. - - **mercylimit**: Sets score gap limit to end match, 0 to disable (default 0) - - **noplayerstime**: Sets time in minutes in which there have been no players to force a change of map, 0 to disable (default 0) - - **roundlimit**: sets number of round wins to win the match in round-based gametypes (default 8) - - **roundtimelimit**: sets round time limit (in minutes) in round-based gametypes (default 2) - -## Level Controls - -### New Items -- Personal Teleporter (item_teleporter): holdable item for deathmatch, teleports the players to a spawn point upon activation. -- Small ammo items for shells, bullets, rockets, cells and slugs (ie: ammo_bullets_small) -- Large ammo items for shells, bullets and cells (ie: ammo_bullets_large) -- Regeneration (item_regen): 30 second powerup regenerates your health up to 2x max health - -### Map Tweaks -Some entity overrides are included which add some subtle ambient sounds, mover sounds, intermission cams and gametype-specific item tweaks. - -### Map Entity Controls - * Map Item Replacement Control: - - **_[classname]** and **[mapname]__[classname]** user cvars to remove or replace specific DM map items (by classname) or only in specific maps if desired - * Save and load .ent files to override entire map entity string, located in baseq2/$g_entity_override_dir$/[mapname].ent: - - **g_entity_override_dir**: overrides entity override file subdir within baseq2 (default: maps) - - **g_entity_override_save**: when set to 1, will save entity override file upon map load (should one not already be loaded) (default: 0) - - **g_entity_override_load**: when set to 1, will load entity override file upon map load (default: 1) - * New entity keys**: "gametype" and "not_gametype": set conditional list of gametypes to respectively spawn or not spawn the entity in. The list can be comma or space separated. The following values correspond to a particular gametype: - campaign: Campaigns - ffa: Deathmatch - tournament: Duel - team: Team Deathmatch - ctf: Capture the Flag - ca: Clan Arena - ft: Freeze Tag - rr: Red Rover - lms: Last Man Standing - horde: Horde Mode - Example: "gametype" "ffa tournament" - this will spawn the entity only in deathmatches and duels. - * New entity keys**: "**notteam**" and "**notfree**": removes an entity from team gametypes or non-team gametypes respectively. - Example: "**notteam**" "1" - the entity will not spawn in team gametypes such as TDM, CTF, FreezeTag and Clan Arena. - * misc_teleporter: **"mins"/"maxs" "x y z"** entity keys to override teleport trigger size, removes teleporter pad if either keys are set - * new item spawnflag & 8: item spawns in suspended state (does not drop to floor) - * Hacky Map Fixes: - * bunk1: button for lift to ware2 now has a wait of -1 (never returns), stops co-op players from pushing the button again and toggling the lift! - * "nobots" and "nohumans": keys for info_player_deathmatch to avoid using for bots or humans respectively - -### Entity Keys -* SPAWNFLAGS: - - spawnflags & 8: suspends items (don't fall to ground) -* Worldspawn: - - **author** and **author2**: sets level author information, this can be seen in the server info submenu. - -### Entity Changes - - misc_nuke: now applies nuke effect (screen flash, earthquake) - - trigger_push: target a target_position/info_notnull to set a direction and apogee like in Q3, no target reverts to original behaviour - - trigger_key: does not remove inventory item in deathmatch, deathmatch or spawnflags 1 now allows multiple uses. - - trigger_coop_relay: annoying "all players must be present" feature in co-op has been removed as it proves a game breaker in games with afk players, always treated like a trigger_relay now - -### New Entities -- target_remove_powerups: - Takes away all the activator's powerups, techs, held items, keys and CTF flags. - -- target_remove_weapons: - Takes away all the activator's weapons and ammo (except blaster). - BLASTER : also remove blaster - -- target_give: - Gives the activator the targetted item. - -- target_delay: - Sets a delay before firing its targets. - "wait" seconds to pause before firing targets. - "random" delay variance, total delay = delay +/- random seconds - -- target_print: - Sends a center-printed message to clients. - "message" text to print - spawnflags: REDTEAM BLUETEAM PRIVATE - If "PRIVATE", only the activator gets the message. If no checks, all clients get the message. - -- target_setskill: - Set skill level. - "message" : skill level to set to (0-3) - Skill levels are: - 0 = Easy - 1 = Medium - 2 = Hard - 3 = Nightmare/Hard+ - -- target_score: - "count" number of points to adjust by, default 1. - The activator is given this many points. - spawnflags: TEAM - TEAM : also adjust team score - -- target_teleporter: - The activator will be teleported to the targetted destination. - If no target set, it will find a player spawn point instead. - -- target_relay: - Correctly named trigger_relay. - -- target_kill: - Kills the activator. - -- target_cvar: - When targetted sets a cvar to a value. - "cvar" : name of cvar to set - "cvarValue" : value to set cvar to - -- target_shooter_grenade: - Fires a grenade in the set direction when triggered. - dmg default is 120 - speed default is 600 - -- target_shooter_rocket: - Fires a rocket in the set direction when triggered. - dmg default is 120 - speed default is 600 - -- target_shooter_bfg: - Fires a BFG projectile in the set direction when triggered. - dmg default is 200 in DM, 500 in campaigns - speed default is 400 - -- target_shooter_prox: - Fires a prox mine in the set direction when triggered. - dmg default is 90 - speed default is 600 - -- target_shooter_ionripper: - Fires an ionripper projectile in the set direction when triggered. - dmg default is 20 in DM and 50 in campaigns - speed default is 800 - -- target_shooter_phalanx: - Fires a phalanx projectile in the set direction when triggered. - dmg default is 80 - speed default is 725 - -- target_shooter_flechette: - Fires a flechette in the set direction when triggered. - dmg default is 10 - speed default is 1150 - -- target_position: - Alias for info_notnull. - -- trigger_deathcount: - Fires targets only if minimum death count has been achieved in the level. - Deaths considered are monsters during campaigns and players during deathmatch. - "count" minimum number of deaths required (default 10) - spawnflags: REPEAT - REPEAT : repeats per every 'count' deaths - -- trigger_no_monsters: - Fires targets only if all monsters have been killed or none are present. - Auto-removed in deathmatch (except horde mode). - ONCE : will be removed after firing once - -- trigger_monsters: - Fires targets only if monsters are present in the level. - Auto-removed in deathmatch (except horde mode). - ONCE : will be removed after firing once - -## TODO: -- tastyspleen.net's mymap system: add support for dm flags -- gametype: Freeze Tag (WIP) -- Server-side player configs, stats, Elo, ranked matches, Elo team balancing (WIP) -- Gladiator bots -- Menu overhaul, adding voting, full admin controls, mymap, player config -- Quake II Santuary community for testing. A shout out particularly to Sata, TurboPtys_drk and Jobe for their help. - -## Credits: -- The Stingy Hat Games YouTube channel for their excellent modding tutorial, without it I would never be able to compile the damned source! -- Nightdive team for the impressive remaster, also some on the team who patiently answered all my annoying modding questions (particularly Paril, sponge, Edward850) -- Paril for some of the Horde Mode code (really just the spawn code), [link to Paril's mod](https://github.com/Paril/q2horde) -- id Software, both for Quake II and Quake III Arena (some code ported from the latter) -- ceeeKay for the eyecam code from Q2Eaks -- The Q2Re player community for bug spotting and general feedback +Use **callvote [command] [arg]**: +- **map**: change the level to the specified map; map must be in the map list. +- **nextmap**: force level change to the next map. +- **restart**: force the match to reset to warmup; requires a match in progress. +- **gametype**: change gametype to the specified type (ffa|duel|tdm|ctf|ca|ft|rr|strike|lms|horde). +- **timelimit**: change timelimit to the minutes specified. +- **scorelimit**: change scorelimit to the value specified. +- **shuffle**: shuffle and balance the teams, then reset the match (requires a team gametype). +- **balance**: balance the teams without a shuffle; switches last-joined players from stacked team (requires a team gametype). +- **unlagged**: enable or disable lag compensation. +- **cointoss**: randomly returns either HEADS or TAILS. +- **random**: randomly returns a number from 2 to argument value, 100 max. +- **ruleset **: change gameplay style. From 15d47cbd19d7687b683d655dd8fa7cdf75072d8e Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:09:51 +0000 Subject: [PATCH 079/142] Clamp random target selection index --- src/g_utils_target_selection.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/g_utils_target_selection.h b/src/g_utils_target_selection.h index 5bbee58..9e56101 100644 --- a/src/g_utils_target_selection.h +++ b/src/g_utils_target_selection.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -23,6 +24,10 @@ T *G_SelectRandomTarget(const std::vector &choices, RandomFunc &&random_fun } assert(!choices.empty()); - return choices[std::forward(random_func)(choices.size())]; + + const std::size_t max_index = choices.size() - 1; + const std::size_t generated_index = std::forward(random_func)(max_index); + const std::size_t clamped_index = generated_index > max_index ? max_index : generated_index; + return choices[clamped_index]; } From 4fc5780c84171cba34048acc7a3b1e4b234d9f8d Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:10:14 +0000 Subject: [PATCH 080/142] Prevent ground spawns over water --- src/g_monster_spawn.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index bff4c2a..82432f4 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -182,6 +182,20 @@ PMM - used for walking monsters bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, const vec3_t &gravityVector) { + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + // don't spawn in or above water + if (gi.pointcontents(origin) & MASK_WATER) + return false; + + trace_t contents_trace = gi.trace(origin, entMins, entMaxs, origin + (gravity_dir * height), nullptr, MASK_WATER); + + if ((contents_trace.contents & MASK_WATER) || contents_trace.startsolid || contents_trace.allsolid) + return false; + if (!CheckSpawnPoint(origin, entMins, entMaxs, gravityVector)) return false; From 068927528663b0079231ab069d23664d3bd56d37 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:10:33 +0000 Subject: [PATCH 081/142] Add client guard to CopyToBodyQue --- src/p_client.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/p_client.cpp b/src/p_client.cpp index 18c51c6..8485157 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -2355,7 +2355,20 @@ static THINK(BodySink) (gentity_t* ent) -> void { gi.linkentity(ent); } + +/* +============= +CopyToBodyQue + +Copy the entity state into a body queue slot for later corpse handling. +============= +*/ void CopyToBodyQue(gentity_t* ent) { + if (!ent->client) { + gi.unlinkentity(ent); + return; + } + // if we were completely removed, don't bother with a body if (!ent->s.modelindex) return; From 2d48fb1e2480955d495c3a53db1fb5efaf25d5ec Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:10:58 +0000 Subject: [PATCH 082/142] Clamp shadow light setup to maximum --- src/g_misc.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/g_misc.cpp b/src/g_misc.cpp index 0c59496..652d0c8 100644 --- a/src/g_misc.cpp +++ b/src/g_misc.cpp @@ -494,6 +494,14 @@ struct shadow_light_info_t { static shadow_light_info_t shadowlightinfo[MAX_SHADOW_LIGHTS]; +/* +============= +GetShadowLightData + +Returns the shadow light data for the specified entity number or nullptr when +no matching entry exists. +============= +*/ const shadow_light_data_t *GetShadowLightData(int32_t entity_number) { for (size_t i = 0; i < level.shadow_light_count; i++) { if (shadowlightinfo[i].entity_number == entity_number) @@ -503,7 +511,20 @@ const shadow_light_data_t *GetShadowLightData(int32_t entity_number) { return nullptr; } +/* +============= +setup_shadow_lights + +Initializes shadow light data and configstrings while clamping to the maximum +allowed lights to prevent out-of-bounds access. +============= +*/ void setup_shadow_lights() { + if (level.shadow_light_count < 0) + level.shadow_light_count = 0; + else if (level.shadow_light_count > MAX_SHADOW_LIGHTS) + level.shadow_light_count = MAX_SHADOW_LIGHTS; + for (int i = 0; i < level.shadow_light_count; ++i) { gentity_t *self = &g_entities[shadowlightinfo[i].entity_number]; @@ -544,7 +565,20 @@ void setup_shadow_lights() { // lights to be ordered wrong on return levels // if the spawn functions are changed. // this will work without changing the save/load code. +/* +============= +G_LoadShadowLights + +Restores shadow light data from configstrings while ensuring the light count +stays within valid bounds. +============= +*/ void G_LoadShadowLights() { + if (level.shadow_light_count < 0) + level.shadow_light_count = 0; + else if (level.shadow_light_count > MAX_SHADOW_LIGHTS) + level.shadow_light_count = MAX_SHADOW_LIGHTS; + for (size_t i = 0; i < level.shadow_light_count; i++) { const char *cstr = gi.get_configstring(CS_SHADOWLIGHTS + i); const char *token = COM_ParseEx(&cstr, ";"); @@ -589,9 +623,21 @@ void G_LoadShadowLights() { } // --------------------------------------------------------------------------------- + +/* +============= +setup_dynamic_light + +Initializes a dynamic light entity and tracks it for shadow handling while +respecting the maximum supported light count. +============= +*/ static void setup_dynamic_light(gentity_t *self) { // [Sam-KEX] Shadow stuff if (st.sl.data.radius > 0) { + if (level.shadow_light_count >= MAX_SHADOW_LIGHTS) + return; + self->s.renderfx = RF_CASTSHADOW; self->itemtarget = st.sl.lightstyletarget; From 8f383b2be4da955964fa0c674f3f1f173f8c465b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:11:17 +0000 Subject: [PATCH 083/142] Handle missing targets without asserting --- src/g_utils.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 87f59f1..fb50b1d 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -81,8 +81,8 @@ nullptr will be returned if the end of the list is reached. ============= */ gentity_t* G_PickTarget(const char* targetname) { - std::vector choices; - gentity_t* ent = nullptr; +std::vector choices; +gentity_t* ent = nullptr; if (!targetname) { gi.Com_PrintFmt("{}: called with nullptr targetname.\n", __FUNCTION__); @@ -98,7 +98,6 @@ gentity_t* G_PickTarget(const char* targetname) { if (choices.empty()) { gi.Com_PrintFmt("{}: target {} not found\n", __FUNCTION__, targetname); - assert(!choices.empty()); return nullptr; } From 082cba2fb0074f015a8b920e72e9d016bc753287 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:11:38 +0000 Subject: [PATCH 084/142] Require Autodoc for regen --- src/g_items.cpp | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/g_items.cpp b/src/g_items.cpp index 72d0d88..2385f9a 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -1125,12 +1125,20 @@ void Tech_ApplyTimeAccelSound(gentity_t* ent) { } } +/* +============= +Tech_ApplyAutoDoc + +Applies regeneration benefits when the Autodoc tech is owned. +============= +*/ void Tech_ApplyAutoDoc(gentity_t* ent) { bool noise = false; gclient_t* cl; int index; float volume = 1.0; bool mod = g_instagib->integer || g_nadefest->integer; + bool has_autodoc = false; bool no_health = mod || GTF(GTF_ARENA) || g_no_health->integer; int max = g_vampiric_damage->integer ? ceil(g_vampiric_health_max->integer / 2) : mod ? 100 : 150; @@ -1144,14 +1152,16 @@ void Tech_ApplyAutoDoc(gentity_t* ent) { if (cl->silencer_shots) volume = 0.2f; + has_autodoc = cl->pers.inventory[IT_TECH_AUTODOC]; + + if (!has_autodoc) + return; + if (mod && !cl->tech_regen_time) { cl->tech_regen_time = level.time; return; } - if (!(cl->pers.inventory[IT_TECH_AUTODOC] || mod)) - return; - if (cl->tech_regen_time < level.time) { bool mm = !!(RS(RS_MM)); gtime_t delay = mm ? 1_sec : 500_ms; @@ -1184,12 +1194,21 @@ void Tech_ApplyAutoDoc(gentity_t* ent) { } } +/* +============= +Tech_HasRegeneration + +Returns true if the entity currently benefits from a regeneration effect. +============= +*/ bool Tech_HasRegeneration(gentity_t* ent) { - if (!ent->client) return false; - if (ent->client->pers.inventory[IT_TECH_AUTODOC]) return true; - if (g_instagib->integer) return true; - if (g_nadefest->integer) return true; - return false; + if (!ent || !ent->client) + return false; + + if (ent->client->pu_time_regeneration > level.time) + return true; + + return ent->client->pers.inventory[IT_TECH_AUTODOC]; } // =============================================== From a700f1ad9c6013f44b91d41f361760e7ae828a5a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:12:01 +0000 Subject: [PATCH 085/142] Respect spawn height when checking ground --- src/g_monster_spawn.cpp | 10 ++++++ tests/spawn_gravity_tests.cpp | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index bff4c2a..565dc1d 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -185,6 +185,16 @@ bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const ve if (!CheckSpawnPoint(origin, entMins, entMaxs, gravityVector)) return false; + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + trace_t trace = gi.trace(origin, entMins, entMaxs, origin + (gravity_dir * height), nullptr, MASK_MONSTERSOLID); + + if (trace.fraction == 1.0f) + return false; + if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, gravityVector)) return true; diff --git a/tests/spawn_gravity_tests.cpp b/tests/spawn_gravity_tests.cpp index 1036328..dcd59ca 100644 --- a/tests/spawn_gravity_tests.cpp +++ b/tests/spawn_gravity_tests.cpp @@ -39,6 +39,30 @@ static trace_t TestTrace(const vec3_t &start, const vec3_t &, const vec3_t &, co return tr; } +/* +============= +NoHitTrace + +Returns a full-length trace with no impact to simulate missing ground. +============= +*/ +static trace_t NoHitTrace(const vec3_t &start, const vec3_t &, const vec3_t &, const vec3_t &end, gentity_t *, contents_t) +{ + trace_t tr{}; + + g_last_trace_start = start; + g_last_trace_end = end; + g_trace_calls++; + + tr.ent = world; + tr.endpos = end; + tr.fraction = 1.0f; + tr.startsolid = false; + tr.allsolid = false; + + return tr; +} + /* ============= G_FixStuckObject_Generic @@ -184,6 +208,42 @@ static void TestGroundChecksUseGravityVector() assert(g_last_bottom_slow == gravity); } +/* +============= +TestGroundSpawnHonorsHeight + +Fails ground spawn validation when the surface is beyond the allowed height. +============= +*/ +static void TestGroundSpawnHonorsHeight() +{ + gi.trace = NoHitTrace; + g_trace_calls = 0; + g_last_trace_start = { 0.0f, 0.0f, 0.0f }; + g_last_trace_end = { 0.0f, 0.0f, 0.0f }; + + vec3_t origin{ 0.0f, 0.0f, 0.0f }; + vec3_t mins{ -8.0f, -8.0f, -8.0f }; + vec3_t maxs{ 8.0f, 8.0f, 8.0f }; + vec3_t gravity{ 0.0f, 0.0f, -1.0f }; + float height = 64.0f; + + bool grounded = CheckGroundSpawnPoint(origin, mins, maxs, height, gravity); + + assert(!grounded); + assert(g_trace_calls >= 3); + + vec3_t gravity_dir = gravity.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + vec3_t expected_end = origin + (gravity_dir * height); + + assert(g_last_trace_start == origin); + assert(g_last_trace_end == expected_end); +} + /* ============= main @@ -194,5 +254,6 @@ int main() TestCeilingDropUsesGravity(); TestWallGravityProjectsSpawnVolume(); TestGroundChecksUseGravityVector(); + TestGroundSpawnHonorsHeight(); return 0; } From 19f30842f3cb41c36c510d566ece9771519d9b42 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:12:20 +0000 Subject: [PATCH 086/142] Improve spawn clearance tracing --- src/g_monster_spawn.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index bff4c2a..4ad8962 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -151,6 +151,8 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max if (!gravity_dir) gravity_dir = { 0.0f, 0.0f, -1.0f }; + vec3_t abs_gravity_dir = gravity_dir.abs(); + tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; @@ -158,16 +160,29 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max if (tr.ent != world) return false; - vec3_t projected_origin = origin + gravity_dir; + const float positive_extent = DotProduct(maxs, abs_gravity_dir); + const float negative_extent = Q_fabs(DotProduct(mins, abs_gravity_dir)); - tr = gi.trace(origin, mins, maxs, projected_origin, nullptr, MASK_MONSTERSOLID); - if (tr.startsolid || tr.allsolid) - return false; + if (positive_extent > 0.0f) + { + vec3_t upward_check = origin - (gravity_dir * positive_extent); - return true; -} + tr = gi.trace(origin, mins, maxs, upward_check, nullptr, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + return false; + } + if (negative_extent > 0.0f) + { + vec3_t downward_check = origin + (gravity_dir * negative_extent); + tr = gi.trace(origin, mins, maxs, downward_check, nullptr, MASK_MONSTERSOLID); + if (tr.startsolid || tr.allsolid) + return false; + } + + return true; +} /* ============= CheckGroundSpawnPoint From 8c70f029122058667f888959982d0f5e96bed9b7 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:12:38 +0000 Subject: [PATCH 087/142] Add client null check when closing menu --- src/p_menu.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/p_menu.cpp b/src/p_menu.cpp index 77a0556..47bc42d 100644 --- a/src/p_menu.cpp +++ b/src/p_menu.cpp @@ -71,9 +71,19 @@ menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, return hnd; } +/* +============= +P_Menu_Close + +Closes the active menu for the entity and frees associated resources. +============= +*/ void P_Menu_Close(gentity_t *ent) { menu_hnd_t *hnd; + if (!ent->client) + return; + if (!ent->client->menu) return; @@ -84,7 +94,7 @@ void P_Menu_Close(gentity_t *ent) { gi.TagFree(hnd); ent->client->menu = nullptr; ent->client->showscores = false; - + gentity_t *e = ent->client->follow_target ? ent->client->follow_target : ent; ent->client->ps.stats[STAT_SHOW_STATUSBAR] = !ClientIsPlaying(e->client) ? 0 : 1; } From 36e5207183e021415dd975b76cbaeecfcac66a22 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 00:12:57 +0000 Subject: [PATCH 088/142] Fix menu text copy bounds and add coverage --- src/p_menu.cpp | 17 +++++++++++------ tests/menu_copy_tests.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 tests/menu_copy_tests.cpp diff --git a/src/p_menu.cpp b/src/p_menu.cpp index 77a0556..377f571 100644 --- a/src/p_menu.cpp +++ b/src/p_menu.cpp @@ -1,6 +1,7 @@ // Copyright (c) ZeniMax Media Inc. // Licensed under the GNU General Public License 2.0. #include "g_local.h" +#include /* ============ @@ -15,10 +16,13 @@ void P_Menu_Dirty() { } } -// Note that the pmenu entries are duplicated -// this is so that a static set of pmenu entries can be used -// for multiple clients and changed without interference -// note that arg will be freed when the menu is closed, it must be allocated memory +/* +============= +P_Menu_Open + +Open a menu for a client and duplicate entry data for safe modification. +============= +*/ menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc) { menu_hnd_t *hnd; const menu_t *p; @@ -40,8 +44,9 @@ menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, hnd->entries = (menu_t *)gi.TagMalloc(sizeof(menu_t) * num, TAG_LEVEL); memcpy(hnd->entries, entries, sizeof(menu_t) * num); // duplicate the strings since they may be from static memory - for (i = 0; i < num; i++) - Q_strlcpy(hnd->entries[i].text, entries[i].text, sizeof(entries[i].text)); + for (i = 0; i < num; i++) { + assert(Q_strlcpy(hnd->entries[i].text, entries[i].text, sizeof(hnd->entries[i].text)) < sizeof(hnd->entries[i].text)); + } hnd->num = num; diff --git a/tests/menu_copy_tests.cpp b/tests/menu_copy_tests.cpp new file mode 100644 index 0000000..d33ea8a --- /dev/null +++ b/tests/menu_copy_tests.cpp @@ -0,0 +1,31 @@ +#include "../src/q_std.h" +#include +#include + +/* +============= +main + +Verify Q_strlcpy copies menu-sized buffers without truncation when the destination size is provided. +============= +*/ +int main() +{ + char destination[256] = {}; + const char short_source[] = "Short label"; + const size_t copied = Q_strlcpy(destination, short_source, sizeof(destination)); + + assert(copied == strlen(short_source)); + assert(strcmp(destination, short_source) == 0); + + char long_source[300]; + memset(long_source, 'a', sizeof(long_source)); + long_source[sizeof(long_source) - 1] = '\0'; + + char truncated[16] = {}; + const size_t truncated_length = Q_strlcpy(truncated, long_source, sizeof(truncated)); + assert(truncated_length >= sizeof(truncated)); + assert(truncated[sizeof(truncated) - 1] == '\0'); + + return 0; +} From 0d3386173ae844da2751c1ecab3b473849dc27e8 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:07:15 +0000 Subject: [PATCH 089/142] Improve README header styling --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab26c30..6bf87d4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - +
    Muff Mode Logo From 9fa2ea95b804f5bb1960ac78c8f8acab3f385d4c Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:19:45 +0000 Subject: [PATCH 090/142] Initialize activation message plan --- src/g_activation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/g_activation.cpp b/src/g_activation.cpp index 8f9e06e..389b487 100644 --- a/src/g_activation.cpp +++ b/src/g_activation.cpp @@ -9,7 +9,7 @@ Creates an activation message plan for the provided context. */ activation_message_plan_t BuildActivationMessagePlan(bool has_message, bool has_activator, bool activator_is_monster, bool coop_global, bool coop_enabled, int noise_index) { - activation_message_plan_t plan; + activation_message_plan_t plan{}; if (!has_message) return plan; From a1df643198b9c067f3fb58fb7e92ed4dfbd01f56 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:20:21 +0000 Subject: [PATCH 091/142] Handle failed monster spawns in ground creation tests --- src/g_monster_spawn.cpp | 13 +++++++- tests/spawn_gravity_tests.cpp | 59 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index da233fc..cee4bd5 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -43,10 +43,21 @@ gentity_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char newEnt->gravityVector = { 0, 0, -1 }; ED_CallSpawn(newEnt); newEnt->s.renderfx |= RF_IR_VISIBLE; - + return newEnt; } +#else +/* +============= +CreateMonster + +Provided by unit tests when MONSTER_SPAWN_TESTS is defined. +============= +*/ +gentity_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname); +#endif + /* ============= CreateFlyMonster diff --git a/tests/spawn_gravity_tests.cpp b/tests/spawn_gravity_tests.cpp index dcd59ca..5181963 100644 --- a/tests/spawn_gravity_tests.cpp +++ b/tests/spawn_gravity_tests.cpp @@ -14,6 +14,40 @@ static vec3_t g_last_trace_start; static vec3_t g_last_trace_end; static vec3_t g_last_bottom_fast; static vec3_t g_last_bottom_slow; +static bool g_create_monster_should_fail = false; + +/* +============= +TestPointContents + +Treats all points as non-water for spawn validation. +============= +*/ +static contents_t TestPointContents(const vec3_t &) +{ + return 0; +} + +/* +============= +CreateMonster + +Returns nullptr when configured to simulate spawn failure. +============= +*/ +gentity_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname) +{ + if (g_create_monster_should_fail) + return nullptr; + + static gentity_t dummy_ent{}; + + dummy_ent.s.origin = origin; + dummy_ent.s.angles = angles; + dummy_ent.classname = classname; + + return &dummy_ent; +} /* ============= @@ -244,6 +278,29 @@ static void TestGroundSpawnHonorsHeight() assert(g_last_trace_end == expected_end); } +/* +============= +TestCreateGroundMonsterReturnsNullOnFailedSpawn + +Ensures CreateGroundMonster returns nullptr when CreateMonster fails. +============= +*/ +static void TestCreateGroundMonsterReturnsNullOnFailedSpawn() +{ + gi.trace = TestTrace; + gi.pointcontents = TestPointContents; + g_create_monster_should_fail = true; + + vec3_t origin{ 16.0f, 8.0f, 4.0f }; + vec3_t angles{ 0.0f, 90.0f, 0.0f }; + vec3_t mins{ -8.0f, -8.0f, -8.0f }; + vec3_t maxs{ 8.0f, 8.0f, 8.0f }; + + gentity_t *spawned = CreateGroundMonster(origin, angles, mins, maxs, "monster_infantry", 64.0f); + + assert(spawned == nullptr); +} + /* ============= main @@ -251,9 +308,11 @@ main */ int main() { + gi.pointcontents = TestPointContents; TestCeilingDropUsesGravity(); TestWallGravityProjectsSpawnVolume(); TestGroundChecksUseGravityVector(); TestGroundSpawnHonorsHeight(); + TestCreateGroundMonsterReturnsNullOnFailedSpawn(); return 0; } From 4a3194bd6f207687381ae008d623a122f9b556a0 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:20:43 +0000 Subject: [PATCH 092/142] Clear spawnpoint on failed stuck resolution --- src/g_monster_spawn.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index da233fc..9ba6b67 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -117,7 +117,10 @@ bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t & if (G_FixStuckObject_Generic(spawnpoint, mins, maxs, [] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { return gi.trace(start, mins, maxs, end, nullptr, MASK_MONSTERSOLID); }) == stuck_result_t::NO_GOOD_POSITION) + { + spawnpoint = { 0.0f, 0.0f, 0.0f }; return false; + } // fixed, so drop again if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false)) From 78733d21aa5c498df42780ce5c8fb3b040111464 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:21:17 +0000 Subject: [PATCH 093/142] Make menu argument ownership explicit --- src/g_menu.cpp | 26 +++++++++++++------------- src/p_menu.cpp | 41 ++++++++++++++++++++++------------------- src/p_menu.h | 3 ++- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/g_menu.cpp b/src/g_menu.cpp index eae9c68..d28944f 100644 --- a/src/g_menu.cpp +++ b/src/g_menu.cpp @@ -228,7 +228,7 @@ static void G_Menu_Admin_Settings(gentity_t *ent, menu_hnd_t *p) { settings->instantweap = g_instant_weapon_switch->integer != 0; settings->match_lock = g_match_lock->integer != 0; - menu = P_Menu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(menu_t), settings, nullptr); + menu = P_Menu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(menu_t), settings, true, nullptr); G_Menu_Admin_UpdateSettings(ent, menu); } @@ -285,7 +285,7 @@ void G_Menu_Admin(gentity_t *ent, menu_hnd_t *p) { } P_Menu_Close(ent); - P_Menu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(menu_t), nullptr, nullptr); + P_Menu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(menu_t), nullptr, false, nullptr); } /*-----------------------------------------------------------------------*/ @@ -399,7 +399,7 @@ static void G_Menu_PMStats_Update(gentity_t *ent) { static void G_Menu_PMStats(gentity_t *ent, menu_hnd_t *p) { P_Menu_Close(ent); - P_Menu_Open(ent, pmstatsmenu, -1, sizeof(pmstatsmenu) / sizeof(menu_t), nullptr, G_Menu_PMStats_Update); + P_Menu_Open(ent, pmstatsmenu, -1, sizeof(pmstatsmenu) / sizeof(menu_t), nullptr, false, G_Menu_PMStats_Update); } /*-----------------------------------------------------------------------*/ @@ -563,7 +563,7 @@ static void G_Menu_CallVote_Map_Update(gentity_t *ent) { void G_Menu_CallVote_Map(gentity_t *ent, menu_hnd_t *p) { P_Menu_Close(ent); - P_Menu_Open(ent, pmcallvotemenu_map, -1, sizeof(pmcallvotemenu_map) / sizeof(menu_t), nullptr, G_Menu_CallVote_Map_Update); + P_Menu_Open(ent, pmcallvotemenu_map, -1, sizeof(pmcallvotemenu_map) / sizeof(menu_t), nullptr, false, G_Menu_CallVote_Map_Update); } void G_Menu_CallVote_NextMap(gentity_t *ent, menu_hnd_t *p) { @@ -594,7 +594,7 @@ void G_Menu_CallVote_TimeLimit(gentity_t *ent, menu_hnd_t *p) { //level.vote_arg.clear(); //VoteCommandStore(ent); P_Menu_Close(ent); - P_Menu_Open(ent, pmcallvotemenu_timelimit, -1, sizeof(pmcallvotemenu_timelimit) / sizeof(menu_t), nullptr, G_Menu_CallVote_TimeLimit_Update); + P_Menu_Open(ent, pmcallvotemenu_timelimit, -1, sizeof(pmcallvotemenu_timelimit) / sizeof(menu_t), nullptr, false, G_Menu_CallVote_TimeLimit_Update); } void G_Menu_CallVote_ScoreLimit(gentity_t *ent, menu_hnd_t *p) { @@ -665,7 +665,7 @@ static void G_Menu_CallVote_Update(gentity_t *ent) { static void G_Menu_CallVote(gentity_t *ent, menu_hnd_t *p) { P_Menu_Close(ent); - P_Menu_Open(ent, pmcallvotemenu, -1, sizeof(pmcallvotemenu) / sizeof(menu_t), nullptr, G_Menu_CallVote_Update); + P_Menu_Open(ent, pmcallvotemenu, -1, sizeof(pmcallvotemenu) / sizeof(menu_t), nullptr, false, G_Menu_CallVote_Update); } /*-----------------------------------------------------------------------*/ @@ -750,7 +750,7 @@ static void G_Menu_Vote_Update(gentity_t *ent) { } void G_Menu_Vote_Open(gentity_t *ent) { - P_Menu_Open(ent, votemenu, -1, sizeof(votemenu) / sizeof(menu_t), nullptr, G_Menu_Vote_Update); + P_Menu_Open(ent, votemenu, -1, sizeof(votemenu) / sizeof(menu_t), nullptr, false, G_Menu_Vote_Update); } @@ -925,7 +925,7 @@ void G_Menu_ChaseCam(gentity_t *ent, menu_hnd_t *p) { GetFollowTarget(ent); P_Menu_Close(ent); - P_Menu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(menu_t), nullptr, G_Menu_NoChaseCamUpdate); + P_Menu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(menu_t), nullptr, false, G_Menu_NoChaseCamUpdate); } void G_Menu_ReturnToMain(gentity_t *ent, menu_hnd_t *p) { @@ -973,7 +973,7 @@ static void G_Menu_HostInfo_Update(gentity_t *ent) { void G_Menu_HostInfo(gentity_t *ent, menu_hnd_t *p) { P_Menu_Close(ent); - P_Menu_Open(ent, hostinfomenu, -1, sizeof(hostinfomenu) / sizeof(menu_t), nullptr, G_Menu_HostInfo_Update); + P_Menu_Open(ent, hostinfomenu, -1, sizeof(hostinfomenu) / sizeof(menu_t), nullptr, false, G_Menu_HostInfo_Update); } static void G_Menu_ServerInfo_Update(gentity_t *ent) { @@ -1187,7 +1187,7 @@ static void G_Menu_ServerInfo_Update(gentity_t *ent) { void G_Menu_ServerInfo(gentity_t *ent, menu_hnd_t *p) { P_Menu_Close(ent); - P_Menu_Open(ent, svinfomenu, -1, sizeof(svinfomenu) / sizeof(menu_t), nullptr, G_Menu_ServerInfo_Update); + P_Menu_Open(ent, svinfomenu, -1, sizeof(svinfomenu) / sizeof(menu_t), nullptr, false, G_Menu_ServerInfo_Update); } static void G_Menu_GameRules_Update(gentity_t *ent) { @@ -1202,7 +1202,7 @@ static void G_Menu_GameRules_Update(gentity_t *ent) { static void G_Menu_GameRules(gentity_t *ent, menu_hnd_t *p) { P_Menu_Close(ent); - P_Menu_Open(ent, svinfomenu, -1, sizeof(svinfomenu) / sizeof(menu_t), nullptr, G_Menu_GameRules_Update); + P_Menu_Open(ent, svinfomenu, -1, sizeof(svinfomenu) / sizeof(menu_t), nullptr, false, G_Menu_GameRules_Update); } static void G_Menu_Join_Update(gentity_t *ent) { @@ -1367,8 +1367,8 @@ void G_Menu_Join_Open(gentity_t *ent) { else team = brandom() ? TEAM_RED : TEAM_BLUE; - P_Menu_Open(ent, teams_join_menu, team, sizeof(teams_join_menu) / sizeof(menu_t), nullptr, G_Menu_Join_Update); + P_Menu_Open(ent, teams_join_menu, team, sizeof(teams_join_menu) / sizeof(menu_t), nullptr, false, G_Menu_Join_Update); } else { - P_Menu_Open(ent, free_join_menu, TEAM_FREE, sizeof(free_join_menu) / sizeof(menu_t), nullptr, G_Menu_Join_Update); + P_Menu_Open(ent, free_join_menu, TEAM_FREE, sizeof(free_join_menu) / sizeof(menu_t), nullptr, false, G_Menu_Join_Update); } } diff --git a/src/p_menu.cpp b/src/p_menu.cpp index a1cff98..985ac8f 100644 --- a/src/p_menu.cpp +++ b/src/p_menu.cpp @@ -20,10 +20,11 @@ void P_Menu_Dirty() { ============= P_Menu_Open -Open a menu for a client and duplicate entry data for safe modification. +Open a menu for a client and duplicate entry data for safe modification. The +owns_arg flag controls whether the menu should free arg on close. ============= */ -menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc) { +menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, void *arg, bool owns_arg, UpdateFunc_t UpdateFunc) { menu_hnd_t *hnd; const menu_t *p; size_t i; @@ -41,6 +42,7 @@ menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, hnd->UpdateFunc = UpdateFunc; hnd->arg = arg; + hnd->owns_arg = owns_arg; hnd->entries = (menu_t *)gi.TagMalloc(sizeof(menu_t) * num, TAG_LEVEL); memcpy(hnd->entries, entries, sizeof(menu_t) * num); // duplicate the strings since they may be from static memory @@ -94,7 +96,7 @@ void P_Menu_Close(gentity_t *ent) { hnd = ent->client->menu; gi.TagFree(hnd->entries); - if (hnd->arg) + if (hnd->owns_arg && hnd->arg) gi.TagFree(hnd->arg); gi.TagFree(hnd); ent->client->menu = nullptr; @@ -104,6 +106,7 @@ void P_Menu_Close(gentity_t *ent) { ent->client->ps.stats[STAT_SHOW_STATUSBAR] = !ClientIsPlaying(e->client) ? 0 : 1; } + // only use on pmenu's that have been called with P_Menu_Open void P_Menu_UpdateEntry(menu_t *entry, const char *text, int align, SelectFunc_t SelectFunc) { Q_strlcpy(entry->text, text, sizeof(entry->text)); @@ -294,39 +297,39 @@ void P_Menu_Select(gentity_t *ent) { namespace { constexpr const char *BANNED_MENU_LINES[] = { - "You are banned from this mod", - "due to extremely poor behaviour", - "towards the community." + "You are banned from this mod", + "due to extremely poor behaviour", + "towards the community." }; menu_t banned_menu_entries[] = { - { "", MENU_ALIGN_CENTER, nullptr }, - { "", MENU_ALIGN_CENTER, nullptr }, - { "", MENU_ALIGN_CENTER, nullptr }, + { "", MENU_ALIGN_CENTER, nullptr }, + { "", MENU_ALIGN_CENTER, nullptr }, + { "", MENU_ALIGN_CENTER, nullptr }, }; void P_Menu_Banned_Update(gentity_t *ent) { - (void)ent; + (void)ent; } void P_Menu_Banned_InitEntries() { - for (size_t i = 0; i < sizeof(banned_menu_entries) / sizeof(banned_menu_entries[0]); ++i) - Q_strlcpy(banned_menu_entries[i].text, BANNED_MENU_LINES[i], sizeof(banned_menu_entries[i].text)); + for (size_t i = 0; i < sizeof(banned_menu_entries) / sizeof(banned_menu_entries[0]); ++i) + Q_strlcpy(banned_menu_entries[i].text, BANNED_MENU_LINES[i], sizeof(banned_menu_entries[i].text)); } } // namespace void P_Menu_OpenBanned(gentity_t *ent) { - if (!ent->client) - return; + if (!ent->client) + return; - P_Menu_Banned_InitEntries(); - P_Menu_Open(ent, banned_menu_entries, -1, sizeof(banned_menu_entries) / sizeof(menu_t), nullptr, P_Menu_Banned_Update); + P_Menu_Banned_InitEntries(); + P_Menu_Open(ent, banned_menu_entries, -1, sizeof(banned_menu_entries) / sizeof(menu_t), nullptr, false, P_Menu_Banned_Update); } bool P_Menu_IsBannedMenu(const menu_hnd_t *hnd) { - if (!hnd) - return false; + if (!hnd) + return false; - return hnd->UpdateFunc == P_Menu_Banned_Update; + return hnd->UpdateFunc == P_Menu_Banned_Update; } diff --git a/src/p_menu.h b/src/p_menu.h index 7b27561..09fddf5 100644 --- a/src/p_menu.h +++ b/src/p_menu.h @@ -16,6 +16,7 @@ struct menu_hnd_t { int cur; int num; void *arg; + bool owns_arg; UpdateFunc_t UpdateFunc; }; @@ -29,7 +30,7 @@ struct menu_t { }; void P_Menu_Dirty(); -menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc); +menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, void *arg, bool owns_arg, UpdateFunc_t UpdateFunc); void P_Menu_Close(gentity_t *ent); void P_Menu_UpdateEntry(menu_t *entry, const char *text, int align, SelectFunc_t SelectFunc); void P_Menu_Do_Update(gentity_t *ent); From 96e98581ee8873d304b03e0856abc7fa124f454e Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:21:47 +0000 Subject: [PATCH 094/142] Improve friendly message validation --- src/g_utils_friendly_message.h | 6 ++++-- tests/friendly_message_tests.cpp | 12 +++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/g_utils_friendly_message.h b/src/g_utils_friendly_message.h index e169695..ab70cbd 100644 --- a/src/g_utils_friendly_message.h +++ b/src/g_utils_friendly_message.h @@ -2,12 +2,14 @@ ============= FriendlyMessageHasText -Determines if the provided message pointer is non-null and non-empty. +Determines if the provided message pointer is non-null, non-empty, and +contains a terminator within a limited scan. ============= */ #pragma once +#include inline bool FriendlyMessageHasText(const char *msg) { - return msg && *msg; + return msg && *msg && strnlen(msg, 256) < 256; } diff --git a/tests/friendly_message_tests.cpp b/tests/friendly_message_tests.cpp index b06d274..71d32ce 100644 --- a/tests/friendly_message_tests.cpp +++ b/tests/friendly_message_tests.cpp @@ -1,5 +1,6 @@ #include "../src/g_utils_friendly_message.h" #include +#include /* ============= @@ -9,10 +10,19 @@ Regression coverage for friendly message validation. ============= */ int main() -{ + { assert(!FriendlyMessageHasText(nullptr)); assert(!FriendlyMessageHasText("")); assert(FriendlyMessageHasText("hello")); + char unterminated[256]; + std::fill_n(unterminated, 256, 'a'); + assert(!FriendlyMessageHasText(unterminated)); + + char bounded[256]; + std::fill_n(bounded, 255, 'b'); + bounded[255] = '\0'; + assert(FriendlyMessageHasText(bounded)); + return 0; } From 6f7c91ae7704f70a0aa6c498f75cc32e27f4d080 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:22:18 +0000 Subject: [PATCH 095/142] Handle invalid random target indices --- src/g_utils_target_selection.h | 11 ++++++++--- src/tests/g_utils_target_selection_test.cpp | 12 +++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/g_utils_target_selection.h b/src/g_utils_target_selection.h index 9e56101..b649b5b 100644 --- a/src/g_utils_target_selection.h +++ b/src/g_utils_target_selection.h @@ -27,7 +27,12 @@ T *G_SelectRandomTarget(const std::vector &choices, RandomFunc &&random_fun const std::size_t max_index = choices.size() - 1; const std::size_t generated_index = std::forward(random_func)(max_index); - const std::size_t clamped_index = generated_index > max_index ? max_index : generated_index; - return choices[clamped_index]; -} + + if (generated_index > max_index) { + std::fprintf(stderr, "%s: generated index %zu exceeds max index %zu.\n", __FUNCTION__, generated_index, max_index); + return nullptr; + } + + return choices[generated_index]; + } diff --git a/src/tests/g_utils_target_selection_test.cpp b/src/tests/g_utils_target_selection_test.cpp index 5b3cc16..a1d1194 100644 --- a/src/tests/g_utils_target_selection_test.cpp +++ b/src/tests/g_utils_target_selection_test.cpp @@ -5,13 +5,13 @@ #include "../g_utils_target_selection.h" struct DummyTarget { -}; + }; /* ============= main -Validates that the target selection helper can pick entries beyond the first eight options. +Validates that the target selection helper can pick entries beyond the first eight options and rejects invalid random indices. ============= */ int main() { @@ -23,7 +23,7 @@ int main() { references.push_back(&target); auto cycling_random = [index = static_cast(0)](size_t max) mutable { - return (index++) % static_cast(max); + return (index++) % static_cast(max + 1); }; bool saw_ninth_entry = false; @@ -38,5 +38,11 @@ int main() { std::vector empty_choices; assert(G_SelectRandomTarget(empty_choices, cycling_random) == nullptr); + + auto bad_random = [](size_t) { + return static_cast(100); + }; + + assert(G_SelectRandomTarget(references, bad_random) == nullptr); return 0; } From ac21893829baa0a83c6d2ca418dd379b6624060f Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:22:39 +0000 Subject: [PATCH 096/142] Refine activation message test assertions --- tests/activation_message_tests.cpp | 42 +++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/tests/activation_message_tests.cpp b/tests/activation_message_tests.cpp index be25e2b..50c393c 100644 --- a/tests/activation_message_tests.cpp +++ b/tests/activation_message_tests.cpp @@ -1,5 +1,23 @@ #include "../src/g_activation.h" -#include +#include + +static int g_failures = 0; + +/* +============= +Expect + +Reports a failed expectation and increments the failure counter. +============= +*/ +static void Expect(bool condition, const char *message) +{ + if (!condition) + { + std::fprintf(stderr, "Expectation failed: %s\n", message); + ++g_failures; + } +} /* ============= @@ -11,20 +29,20 @@ Regression coverage for activation message planning. int main() { activation_message_plan_t no_activator_broadcast = BuildActivationMessagePlan(true, false, false, true, true, 5); - assert(no_activator_broadcast.broadcast_global); - assert(!no_activator_broadcast.center_on_activator); - assert(!no_activator_broadcast.play_sound); + Expect(no_activator_broadcast.broadcast_global, "no_activator_broadcast.broadcast_global should be true"); + Expect(!no_activator_broadcast.center_on_activator, "no_activator_broadcast.center_on_activator should be false"); + Expect(!no_activator_broadcast.play_sound, "no_activator_broadcast.play_sound should be false"); activation_message_plan_t no_activator_silent = BuildActivationMessagePlan(true, false, false, false, true, 2); - assert(!no_activator_silent.broadcast_global); - assert(!no_activator_silent.center_on_activator); - assert(!no_activator_silent.play_sound); + Expect(!no_activator_silent.broadcast_global, "no_activator_silent.broadcast_global should be false"); + Expect(!no_activator_silent.center_on_activator, "no_activator_silent.center_on_activator should be false"); + Expect(!no_activator_silent.play_sound, "no_activator_silent.play_sound should be false"); activation_message_plan_t player_plan = BuildActivationMessagePlan(true, true, false, false, true, 0); - assert(!player_plan.broadcast_global); - assert(player_plan.center_on_activator); - assert(player_plan.play_sound); - assert(player_plan.sound_index == 0); + Expect(!player_plan.broadcast_global, "player_plan.broadcast_global should be false"); + Expect(player_plan.center_on_activator, "player_plan.center_on_activator should be true"); + Expect(player_plan.play_sound, "player_plan.play_sound should be true"); + Expect(player_plan.sound_index == 0, "player_plan.sound_index should be 0"); - return 0; + return g_failures == 0 ? 0 : 1; } From b34452eaba44793d7f5d7209be0fa9628702c01f Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:23:15 +0000 Subject: [PATCH 097/142] Fix gravity trace projection for spawn checks --- src/g_monster_spawn.cpp | 23 ++++++++------- tests/spawn_gravity_tests.cpp | 54 ++++++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index da233fc..9b34ed8 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -152,30 +152,33 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max gravity_dir = { 0.0f, 0.0f, -1.0f }; vec3_t abs_gravity_dir = gravity_dir.abs(); - + tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; - + if (tr.ent != world) return false; - + const float positive_extent = DotProduct(maxs, abs_gravity_dir); const float negative_extent = Q_fabs(DotProduct(mins, abs_gravity_dir)); - + + vec3_t upward_projection = gravity_dir * positive_extent; + vec3_t downward_projection = gravity_dir * negative_extent; + if (positive_extent > 0.0f) { - vec3_t upward_check = origin - (gravity_dir * positive_extent); - + vec3_t upward_check = origin - upward_projection; + tr = gi.trace(origin, mins, maxs, upward_check, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; } - + if (negative_extent > 0.0f) { - vec3_t downward_check = origin + (gravity_dir * negative_extent); - + vec3_t downward_check = origin + downward_projection; + tr = gi.trace(origin, mins, maxs, downward_check, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; @@ -230,7 +233,7 @@ bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const ve if (M_CheckBottom_Slow_Generic(origin, entMins, entMaxs, nullptr, MASK_MONSTERSOLID, gravityVector, false)) return true; - return false; + return false; } diff --git a/tests/spawn_gravity_tests.cpp b/tests/spawn_gravity_tests.cpp index dcd59ca..5b53e57 100644 --- a/tests/spawn_gravity_tests.cpp +++ b/tests/spawn_gravity_tests.cpp @@ -169,18 +169,63 @@ static void TestWallGravityProjectsSpawnVolume() { gi.trace = TestTrace; g_trace_calls = 0; - + vec3_t origin{ 5.0f, 5.0f, 5.0f }; vec3_t mins{ -2.0f, -2.0f, -2.0f }; vec3_t maxs{ 2.0f, 2.0f, 2.0f }; vec3_t gravity{ 1.0f, 0.0f, 0.0f }; - + bool clear = CheckSpawnPoint(origin, mins, maxs, gravity); + + assert(clear); + assert(g_trace_calls >= 2); + + vec3_t gravity_dir = gravity.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + vec3_t abs_gravity_dir = gravity_dir.abs(); + const float negative_extent = Q_fabs(DotProduct(mins, abs_gravity_dir)); + vec3_t expected_end = origin + (gravity_dir * negative_extent); + + assert(g_last_trace_start == origin); + assert(g_last_trace_end == expected_end); +} + +/* +============= +TestNegativeGravityProjectsSpawnVolume +Ensures CheckSpawnPoint projects extents correctly when gravity points into negative axes. +============= +*/ +static void TestNegativeGravityProjectsSpawnVolume() +{ + gi.trace = TestTrace; + g_trace_calls = 0; + + vec3_t origin{ -12.0f, 4.0f, 8.0f }; + vec3_t mins{ -3.0f, -1.0f, -2.0f }; + vec3_t maxs{ 5.0f, 2.0f, 3.0f }; + vec3_t gravity{ -1.0f, -0.5f, 0.0f }; + + bool clear = CheckSpawnPoint(origin, mins, maxs, gravity); + assert(clear); assert(g_trace_calls >= 2); - vec3_t delta = g_last_trace_end - g_last_trace_start; - assert(delta[0] == gravity[0] && delta[1] == gravity[1] && delta[2] == gravity[2]); + + vec3_t gravity_dir = gravity.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + vec3_t abs_gravity_dir = gravity_dir.abs(); + const float negative_extent = Q_fabs(DotProduct(mins, abs_gravity_dir)); + vec3_t expected_end = origin + (gravity_dir * negative_extent); + + assert(g_last_trace_start == origin); + assert(g_last_trace_end == expected_end); } /* @@ -253,6 +298,7 @@ int main() { TestCeilingDropUsesGravity(); TestWallGravityProjectsSpawnVolume(); + TestNegativeGravityProjectsSpawnVolume(); TestGroundChecksUseGravityVector(); TestGroundSpawnHonorsHeight(); return 0; From 958ad926c722a9f34aab3b0b10ec9f2016813b96 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:28:11 +0000 Subject: [PATCH 098/142] Update scoreboard footer separators --- src/p_hud.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/p_hud.cpp b/src/p_hud.cpp index 8e46253..90434c3 100644 --- a/src/p_hud.cpp +++ b/src/p_hud.cpp @@ -543,10 +543,13 @@ void TeamsScoreboardMessage(gentity_t *ent, gentity_t *killer) { if (total[0] - last[0] > 1) // couldn't fit everyone fmt::format_to(std::back_inserter(string), FMT_STRING("xv -32 yv {} loc_string 1 $g_ctf_and_more {} "), - 42 + (last[0] + 1) * 8, total[0] - last[0] - 1); + 42 + (last[0] + 1) * 8, total[0] - last[0] - 1); if (total[1] - last[1] > 1) // couldn't fit everyone fmt::format_to(std::back_inserter(string), FMT_STRING("xv 208 yv {} loc_string 1 $g_ctf_and_more {} "), - 42 + (last[1] + 1) * 8, total[1] - last[1] - 1); + 42 + (last[1] + 1) * 8, total[1] - last[1] - 1); + + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -24 cstring2 \"{}\" "), "www.darkmatter-quake.com"); + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -12 cstring2 \"{}\" "), "community | tournaments | content | news"); gi.WriteByte(svc_layout); gi.WriteString(string.c_str()); @@ -763,6 +766,9 @@ static void DuelScoreboardMessage(gentity_t *ent, gentity_t *killer) { 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())); else fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -48 cstring2 \"{}\" "), "Show inventory to toggle menu."); + + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -24 cstring2 \"{}\" "), "www.darkmatter-quake.com"); + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -12 cstring2 \"{}\" "), "community | tournaments | content | news"); gi.WriteByte(svc_layout); gi.WriteString(string.c_str()); @@ -936,6 +942,9 @@ void DeathmatchScoreboardMessage(gentity_t *ent, gentity_t *killer) { fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -48 cstring2 \"{}\" "), "Use inventory bind to toggle menu."); } + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -24 cstring2 \"{}\" "), "www.darkmatter-quake.com"); + fmt::format_to(std::back_inserter(string), FMT_STRING("xv 0 yb -12 cstring2 \"{}\" "), "community | tournaments | content | news"); + gi.WriteByte(svc_layout); gi.WriteString(string.c_str()); } From ab7551dbba7b1dcd8dd2c611af98778aa8a64ffa Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:28:38 +0000 Subject: [PATCH 099/142] Handle failed CreateMonster in fly spawn --- src/g_monster_spawn.cpp | 7 ++++++- tests/spawn_gravity_tests.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index cc2e51c..f22a1ae 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -70,7 +70,12 @@ gentity_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const ve if (!CheckSpawnPoint(origin, mins, maxs, { 0.0f, 0.0f, -1.0f })) return nullptr; - return (CreateMonster(origin, angles, classname)); + gentity_t *newEnt = CreateMonster(origin, angles, classname); + + if (!newEnt) + return nullptr; + + return newEnt; } /* diff --git a/tests/spawn_gravity_tests.cpp b/tests/spawn_gravity_tests.cpp index 3560b0b..0612123 100644 --- a/tests/spawn_gravity_tests.cpp +++ b/tests/spawn_gravity_tests.cpp @@ -323,6 +323,31 @@ static void TestGroundSpawnHonorsHeight() assert(g_last_trace_end == expected_end); } +/* +============= +TestCreateFlyMonsterReturnsNullOnFailedSpawn + +Ensures CreateFlyMonster returns nullptr when CreateMonster fails. +============= +*/ +static void TestCreateFlyMonsterReturnsNullOnFailedSpawn() +{ + gi.trace = TestTrace; + gi.pointcontents = TestPointContents; + g_create_monster_should_fail = true; + + vec3_t origin{ 12.0f, -4.0f, 20.0f }; + vec3_t angles{ 0.0f, 45.0f, 0.0f }; + vec3_t mins{ -4.0f, -4.0f, -4.0f }; + vec3_t maxs{ 4.0f, 4.0f, 4.0f }; + + gentity_t *spawned = CreateFlyMonster(origin, angles, mins, maxs, "monster_flyer"); + + assert(spawned == nullptr); + + g_create_monster_should_fail = false; +} + /* ============= TestCreateGroundMonsterReturnsNullOnFailedSpawn @@ -359,6 +384,7 @@ int main() TestNegativeGravityProjectsSpawnVolume(); TestGroundChecksUseGravityVector(); TestGroundSpawnHonorsHeight(); + TestCreateFlyMonsterReturnsNullOnFailedSpawn(); TestCreateGroundMonsterReturnsNullOnFailedSpawn(); return 0; } From f1c8b8f238919ca81c786925fceef1b11bb32bed Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:30:17 +0000 Subject: [PATCH 100/142] Center README header cell content --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bf87d4..a7fe27e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -
    + Muff Mode Logo

    Muff Mode BETA

    From f3c4fa5766aa227799ef9a300329ad36cc671d6e Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:41:50 +0000 Subject: [PATCH 101/142] Update g_monster_spawn.cpp --- src/g_monster_spawn.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index f22a1ae..815a63f 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -103,8 +103,6 @@ gentity_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const return newEnt; } -#endif // MONSTER_SPAWN_TESTS - /* From fa7274700387b3547af04f3b5911703ee20b6b8e Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:44:31 +0000 Subject: [PATCH 102/142] Add bounds-checked menu layout builder --- src/p_menu.cpp | 127 +++++++++++++++----------- src/p_menu.h | 5 ++ src/p_menu_statusbar.cpp | 157 +++++++++++++++++++++++++++++++++ tests/menu_statusbar_tests.cpp | 74 ++++++++++++++++ 4 files changed, 311 insertions(+), 52 deletions(-) create mode 100644 src/p_menu_statusbar.cpp create mode 100644 tests/menu_statusbar_tests.cpp diff --git a/src/p_menu.cpp b/src/p_menu.cpp index 985ac8f..1500d10 100644 --- a/src/p_menu.cpp +++ b/src/p_menu.cpp @@ -107,22 +107,29 @@ void P_Menu_Close(gentity_t *ent) { } -// only use on pmenu's that have been called with P_Menu_Open +/* +============= +P_Menu_UpdateEntry + +Replaces the text and callbacks for a menu entry created by P_Menu_Open. +============= +*/ void P_Menu_UpdateEntry(menu_t *entry, const char *text, int align, SelectFunc_t SelectFunc) { Q_strlcpy(entry->text, text, sizeof(entry->text)); entry->align = align; entry->SelectFunc = SelectFunc; } -#include "g_statusbar.h" +/* +============= +P_Menu_Do_Update +Regenerates the menu status bar layout for the client using bounded string +operations. +============= +*/ void P_Menu_Do_Update(gentity_t *ent) { - int i; - menu_t *p; - int x; - menu_hnd_t *hnd; - const char *t; - bool alt = false; + menu_hnd_t *hnd; if (!ent->client->menu) { gi.Com_Print("Warning: ent has no menu\n"); @@ -134,55 +141,22 @@ void P_Menu_Do_Update(gentity_t *ent) { if (hnd->UpdateFunc) hnd->UpdateFunc(ent); - statusbar_t sb; - - sb.xv(32).yv(8).picn("inventory"); - - for (i = 0, p = hnd->entries; i < hnd->num; i++, p++) { - if (!*(p->text)) - continue; // blank line - - t = p->text; - - if (*t == '*') { - alt = true; - t++; - } + char layout[MAX_STRING_CHARS] = {}; - sb.yv(32 + i * 8); - - const char *loc_func = "loc_string"; - - if (p->align == MENU_ALIGN_CENTER) { - x = 0; - loc_func = "loc_cstring"; - } else if (p->align == MENU_ALIGN_RIGHT) { - x = 260; - loc_func = "loc_rstring"; - } else - x = 64; - - sb.xv(x); - - sb.sb << loc_func; - - if (hnd->cur == i || alt) - sb.sb << '2'; - - sb.sb << " 1 \"" << t << "\" \"" << p->text_arg1 << "\" "; - - if (hnd->cur == i) { - sb.xv(56); - sb.string2("\">\""); - } - - alt = false; - } + P_Menu_BuildStatusBar(hnd, layout, sizeof(layout)); gi.WriteByte(svc_layout); - gi.WriteString(sb.sb.str().c_str()); + gi.WriteString(layout); } + +/* +============= +P_Menu_Update + +Performs periodic menu updates when the client is viewing a menu. +============= +*/ void P_Menu_Update(gentity_t *ent) { if (!ent->client->menu) { gi.Com_Print("Warning: ent has no menu\n"); @@ -202,6 +176,13 @@ void P_Menu_Update(gentity_t *ent) { gi.local_sound(ent, CHAN_AUTO, gi.soundindex("misc/menu2.wav"), 1, ATTN_NONE, 0); } +/* +============= +P_Menu_Next + +Advances the menu cursor to the next selectable entry. +============= +*/ void P_Menu_Next(gentity_t *ent) { menu_hnd_t *hnd; int i; @@ -235,6 +216,13 @@ void P_Menu_Next(gentity_t *ent) { P_Menu_Update(ent); } +/* +============= +P_Menu_Prev + +Moves the menu cursor to the previous selectable entry. +============= +*/ void P_Menu_Prev(gentity_t *ent) { menu_hnd_t *hnd; int i; @@ -269,6 +257,13 @@ void P_Menu_Prev(gentity_t *ent) { P_Menu_Update(ent); } +/* +============= +P_Menu_Select + +Triggers the select callback for the current menu entry. +============= +*/ void P_Menu_Select(gentity_t *ent) { menu_hnd_t *hnd; menu_t *p; @@ -308,10 +303,24 @@ menu_t banned_menu_entries[] = { { "", MENU_ALIGN_CENTER, nullptr }, }; +/* +============= +P_Menu_Banned_Update + +No-op update hook for the banned menu. +============= +*/ void P_Menu_Banned_Update(gentity_t *ent) { (void)ent; } +/* +============= +P_Menu_Banned_InitEntries + +Initializes the static banned menu lines. +============= +*/ void P_Menu_Banned_InitEntries() { for (size_t i = 0; i < sizeof(banned_menu_entries) / sizeof(banned_menu_entries[0]); ++i) Q_strlcpy(banned_menu_entries[i].text, BANNED_MENU_LINES[i], sizeof(banned_menu_entries[i].text)); @@ -319,6 +328,13 @@ void P_Menu_Banned_InitEntries() { } // namespace +/* +============= +P_Menu_OpenBanned + +Opens the banned notification menu for a client. +============= +*/ void P_Menu_OpenBanned(gentity_t *ent) { if (!ent->client) return; @@ -327,6 +343,13 @@ void P_Menu_OpenBanned(gentity_t *ent) { P_Menu_Open(ent, banned_menu_entries, -1, sizeof(banned_menu_entries) / sizeof(menu_t), nullptr, false, P_Menu_Banned_Update); } +/* +============= +P_Menu_IsBannedMenu + +Returns true when the supplied menu handle is the banned menu. +============= +*/ bool P_Menu_IsBannedMenu(const menu_hnd_t *hnd) { if (!hnd) return false; diff --git a/src/p_menu.h b/src/p_menu.h index 09fddf5..4b1dfc9 100644 --- a/src/p_menu.h +++ b/src/p_menu.h @@ -1,6 +1,10 @@ // Copyright (c) ZeniMax Media Inc. // Licensed under the GNU General Public License 2.0. +#include + +struct gentity_t; + enum { MENU_ALIGN_LEFT, MENU_ALIGN_CENTER, @@ -33,6 +37,7 @@ void P_Menu_Dirty(); menu_hnd_t *P_Menu_Open(gentity_t *ent, const menu_t *entries, int cur, int num, void *arg, bool owns_arg, UpdateFunc_t UpdateFunc); void P_Menu_Close(gentity_t *ent); void P_Menu_UpdateEntry(menu_t *entry, const char *text, int align, SelectFunc_t SelectFunc); +size_t P_Menu_BuildStatusBar(const menu_hnd_t *hnd, char *layout, size_t layout_size); void P_Menu_Do_Update(gentity_t *ent); void P_Menu_Update(gentity_t *ent); void P_Menu_Next(gentity_t *ent); diff --git a/src/p_menu_statusbar.cpp b/src/p_menu_statusbar.cpp new file mode 100644 index 0000000..f605a94 --- /dev/null +++ b/src/p_menu_statusbar.cpp @@ -0,0 +1,157 @@ +// Copyright (c) ZeniMax Media Inc. +// Licensed under the GNU General Public License 2.0. + +#include "p_menu.h" +#include "q_std.h" + +#include +#include +#include +#include + +#ifdef UNIT_TESTS +/* +============= +Q_strlcpy + +Test-safe implementation of Q_strlcpy used when running unit tests without the +full game import table. +============= +*/ +size_t Q_strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + if (n != 0 && --n != 0) + do { + if ((*d++ = *s++) == '\0') + break; + } while (--n != 0); + + if (n == 0) { + if (siz != 0) + *d = '\0'; + while (*s++) + ; + } + + return static_cast(s - src - 1); +} + +/* +============= +Q_strlcat + +Test-safe implementation of Q_strlcat used when running unit tests without the +full game import table. +============= +*/ +size_t Q_strlcat(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + size_t dlen; + + while (n-- != 0 && *d != '\0') + d++; + dlen = static_cast(d - dst); + n = siz - dlen; + + if (n == 0) + return dlen + std::strlen(s); + + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + + *d = '\0'; + + return dlen + static_cast(s - src); +} +#endif + +/* +============= +P_Menu_Appendf + +Appends formatted text to a layout buffer while ensuring the destination is +not overrun. +============= +*/ +static bool P_Menu_Appendf(char *layout, size_t layout_size, const char *fmt, ...) +{ + if (layout_size == 0) + return false; + + std::vector chunk(layout_size, '\0'); + + va_list args; + va_start(args, fmt); + vsnprintf(chunk.data(), chunk.size(), fmt, args); + va_end(args); + + return Q_strlcat(layout, chunk.data(), layout_size) < layout_size; +} + +/* +============= +P_Menu_BuildStatusBar + +Constructs the status bar layout string for the active menu using a bounded +buffer to avoid overflow. +============= +*/ +size_t P_Menu_BuildStatusBar(const menu_hnd_t *hnd, char *layout, size_t layout_size) +{ + if (!hnd || !layout || layout_size == 0) + return 0; + + layout[0] = '\0'; + + P_Menu_Appendf(layout, layout_size, "xv %d yv %d picn %s ", 32, 8, "inventory"); + + bool alt = false; + + for (int i = 0; i < hnd->num; i++) { + const menu_t *p = hnd->entries + i; + + if (!*(p->text)) + continue; + + const char *t = p->text; + + if (*t == '*') { + alt = true; + t++; + } + + const int y = 32 + i * 8; + int x = 64; + const char *loc_func = "loc_string"; + + if (p->align == MENU_ALIGN_CENTER) { + x = 0; + loc_func = "loc_cstring"; + } else if (p->align == MENU_ALIGN_RIGHT) { + x = 260; + loc_func = "loc_rstring"; + } + + P_Menu_Appendf(layout, layout_size, "yv %d ", y); + P_Menu_Appendf(layout, layout_size, "xv %d %s%s 1 \"%s\" \"%s\" ", x, loc_func, (hnd->cur == i || alt) ? "2" : "", t, p->text_arg1); + + if (hnd->cur == i) + P_Menu_Appendf(layout, layout_size, "xv %d string2 \"%s\" ", 56, ">\""); + + alt = false; + } + + return std::strlen(layout); +} diff --git a/tests/menu_statusbar_tests.cpp b/tests/menu_statusbar_tests.cpp new file mode 100644 index 0000000..d79e1f5 --- /dev/null +++ b/tests/menu_statusbar_tests.cpp @@ -0,0 +1,74 @@ +#include "../src/p_menu.h" +#include "../src/q_std.h" + +#include +#include + +constexpr size_t MAX_STRING_CHARS = 1024; + +/* +============= +MakeMenuEntry + +Constructs a menu_t with the supplied label and default alignment. +============= +*/ +static menu_t MakeMenuEntry(const char *label) +{ + menu_t entry{}; + Q_strlcpy(entry.text, label, sizeof(entry.text)); + entry.align = MENU_ALIGN_LEFT; + entry.SelectFunc = nullptr; + entry.text_arg1[0] = '\0'; + return entry; +} + +/* +============= +main + +Validates that status bar layout construction clamps long menu text safely. +============= +*/ +int main() +{ + menu_t entries[4]; + entries[0] = MakeMenuEntry("Short"); + entries[1] = MakeMenuEntry("*Highlighted"); + entries[2] = MakeMenuEntry("Centered"); + entries[2].align = MENU_ALIGN_CENTER; + entries[3] = MakeMenuEntry("Right"); + entries[3].align = MENU_ALIGN_RIGHT; + + menu_hnd_t hnd{}; + hnd.entries = entries; + hnd.cur = 1; + hnd.num = static_cast(sizeof(entries) / sizeof(entries[0])); + + char layout[MAX_STRING_CHARS] = {}; + const size_t short_length = P_Menu_BuildStatusBar(&hnd, layout, sizeof(layout)); + assert(short_length < MAX_STRING_CHARS); + assert(layout[MAX_STRING_CHARS - 1] == '\0'); + + char oversized_text[sizeof(entries[0].text)]; + std::memset(oversized_text, 'Z', sizeof(oversized_text)); + oversized_text[sizeof(oversized_text) - 1] = '\0'; + + menu_t long_entries[6]; + for (auto &entry : long_entries) + entry = MakeMenuEntry(oversized_text); + + menu_hnd_t long_hnd{}; + long_hnd.entries = long_entries; + long_hnd.cur = 0; + long_hnd.num = static_cast(sizeof(long_entries) / sizeof(long_entries[0])); + + std::memset(layout, 'X', sizeof(layout)); + layout[sizeof(layout) - 1] = '\0'; + const size_t long_length = P_Menu_BuildStatusBar(&long_hnd, layout, sizeof(layout)); + + assert(long_length == MAX_STRING_CHARS - 1); + assert(layout[sizeof(layout) - 1] == '\0'); + + return 0; +} From aa9c755e92734b39ee16fed8a15a402fad0c1ea4 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:47:44 +0000 Subject: [PATCH 103/142] Fix monster spawn vector helpers --- src/g_monster_spawn.cpp | 5 ----- src/q_vec3.h | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index 815a63f..203dcb6 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -234,11 +234,6 @@ bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const ve if (!CheckSpawnPoint(origin, entMins, entMaxs, gravityVector)) return false; - vec3_t gravity_dir = gravityVector.normalized(); - - if (!gravity_dir) - gravity_dir = { 0.0f, 0.0f, -1.0f }; - trace_t trace = gi.trace(origin, entMins, entMaxs, origin + (gravity_dir * height), nullptr, MASK_MONSTERSOLID); if (trace.fraction == 1.0f) diff --git a/src/q_vec3.h b/src/q_vec3.h index 0fa0158..328e91b 100644 --- a/src/q_vec3.h +++ b/src/q_vec3.h @@ -13,6 +13,18 @@ struct vec3_t { float x, y, z; + /* + ============= + abs + + Returns a vector containing the absolute value of each component. + ============= + */ + [[nodiscard]] constexpr vec3_t abs() const + { + return { fabsf(x), fabsf(y), fabsf(z) }; + } + [[nodiscard]] constexpr const float &operator[](size_t i) const { if (i == 0) @@ -168,6 +180,30 @@ struct vec3_t constexpr vec3_t vec3_origin{}; +/* +============= +DotProduct + +Returns the dot product of two vectors. +============= +*/ +[[nodiscard]] inline float DotProduct(const vec3_t &v1, const vec3_t &v2) +{ + return v1.dot(v2); +} + +/* +============= +Q_fabs + +Returns the absolute value of a floating point number. +============= +*/ +[[nodiscard]] inline float Q_fabs(float value) +{ + return fabsf(value); +} + inline void AngleVectors(const vec3_t &angles, vec3_t *forward, vec3_t *right, vec3_t *up) { float angle = angles[YAW] * (PIf * 2 / 360); From e377a4d6e913211c5e2d76c97de53f132f0eb73a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 10:48:21 +0000 Subject: [PATCH 104/142] Center align README header content --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7fe27e..2ca2725 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -
    + Muff Mode Logo

    Muff Mode BETA

    From 5c15534345bb9c1222f78bfe0eeb4ba8ddf4241c Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 11:20:48 +0000 Subject: [PATCH 105/142] Fix vec3 abs constexpr --- src/q_vec3.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/q_vec3.h b/src/q_vec3.h index 328e91b..50e98c3 100644 --- a/src/q_vec3.h +++ b/src/q_vec3.h @@ -4,6 +4,7 @@ #pragma once // q_vec3 - vec3 stuff +#include #include #include @@ -20,7 +21,7 @@ struct vec3_t Returns a vector containing the absolute value of each component. ============= */ - [[nodiscard]] constexpr vec3_t abs() const + [[nodiscard]] inline vec3_t abs() const { return { fabsf(x), fabsf(y), fabsf(z) }; } From 90bed431e7cf6083f38e8b8855f1c5d2e7fe0974 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sat, 22 Nov 2025 11:42:32 +0000 Subject: [PATCH 106/142] Fix CTF CTF spawn check and link status bar source --- src/g_spawn.cpp | 2 +- src/game.vcxproj | 1 + src/game.vcxproj.filters | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index ed2120c..a1cfe7c 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1744,7 +1744,7 @@ entity parsing. ============= */ static void G_TestCTFSpawnPoints() { - if (!GTF(GTF_CTF)) + if (!(GTF(GTF_CTF))) return; auto ensure_linked = [](const char *classname) { diff --git a/src/game.vcxproj b/src/game.vcxproj index f7bc1ee..2b88296 100644 --- a/src/game.vcxproj +++ b/src/game.vcxproj @@ -217,6 +217,7 @@ + diff --git a/src/game.vcxproj.filters b/src/game.vcxproj.filters index 839e7a8..e25c2aa 100644 --- a/src/game.vcxproj.filters +++ b/src/game.vcxproj.filters @@ -276,6 +276,7 @@ monsters + From eccf54a61964d456aa441a4abcac825b3953e5b4 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:02:09 +0000 Subject: [PATCH 107/142] Add null data guard in COM_ParseEx --- src/q_std.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/q_std.cpp b/src/q_std.cpp index 1ff8792..b5281a0 100644 --- a/src/q_std.cpp +++ b/src/q_std.cpp @@ -54,6 +54,14 @@ char *COM_ParseEx(const char **data_p, const char *seps, char *buffer, size_t bu size_t stored_len; const char *data; + if (!data_p || !*data_p) + { + buffer[0] = '\0'; + if (data_p) + *data_p = nullptr; + return buffer; + } + data = *data_p; len = 0; stored_len = 0; From f6a51da14b10a5608997f8aa78762b3cbc779fe7 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:16:04 +0000 Subject: [PATCH 108/142] Add null check to COM_IsSeparator --- src/q_std.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/q_std.cpp b/src/q_std.cpp index 1ff8792..8e9b268 100644 --- a/src/q_std.cpp +++ b/src/q_std.cpp @@ -9,8 +9,18 @@ g_fmt_data_t g_fmt_data; +/* +============= +COM_IsSeparator + +Returns true if the specified character is a separator. +============= +*/ bool COM_IsSeparator(char c, const char *seps) { + if (!seps) + return true; + if (!c) return true; From 7e22ea3a98f8e105dc9ea48e0bf27b18215c840b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:21:16 +0000 Subject: [PATCH 109/142] Ensure save names are stored safely --- src/g_save.cpp | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index cc9270c..8bb4c30 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -25,6 +25,9 @@ constexpr size_t SAVE_FORMAT_VERSION = 1; #include +#include +#include +#include // Professor Daniel J. Bernstein; https://www.partow.net/programming/hashfunctions/#APHashFunction MIT struct cstring_hash { @@ -57,6 +60,28 @@ static const save_data_list_t *list_head = nullptr; static std::unordered_map list_hash; static std::unordered_map list_str_hash; static std::unordered_map, const save_data_list_t *, ptr_tag_hash> list_from_ptr_hash; +static std::vector> list_name_storage; + +/* +============= +StoreListNameCopy + +Duplicates a save data name for safe storage in the string hash map. +============= +*/ +static const char *StoreListNameCopy(const char *name) { + if (!name) + return nullptr; + + const size_t name_len = strlen(name) + 1; + std::unique_ptr stored_name = std::make_unique(name_len); + + memcpy(stored_name.get(), name, name_len); + + list_name_storage.emplace_back(std::move(stored_name)); + + return list_name_storage.back().get(); +} #include @@ -105,10 +130,12 @@ void InitSave() { } list_hash.emplace(link_ptr, link); - list_str_hash.emplace(link->name, link); + const char *stored_name = StoreListNameCopy(link->name); + list_str_hash.emplace(stored_name ? stored_name : link->name, link); list_from_ptr_hash.emplace(std::make_tuple(link->ptr, link->tag), link); } + save_data_initialized = true; } // initializer for save data From 8cc9406957e45c99a6fa7442a570f935ce6401bd Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:21:46 +0000 Subject: [PATCH 110/142] Add null name validation --- src/g_save.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/g_save.cpp b/src/g_save.cpp index cc9270c..7bc216e 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -125,6 +125,11 @@ save_data_list_t::save_data_list_t(const char *name_in, save_data_tag_t tag_in, ptr(ptr_in), next(nullptr), valid(valid_in) { + assert(name_in); + + if (!name_in) + gi.Com_Error("save data entry name cannot be null"); + if (save_data_initialized && link) gi.Com_Error("attempted to create save_data_list at runtime"); From 72d9b917fd9f146e0e8c61718b5282f879f429ad Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:30:24 +0000 Subject: [PATCH 111/142] Add underwater bubble effects for assisted breathing --- src/p_view.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/p_view.cpp b/src/p_view.cpp index 8cc5e1e..992e5af 100644 --- a/src/p_view.cpp +++ b/src/p_view.cpp @@ -689,15 +689,24 @@ static void P_WorldEffects() { if (breather || envirosuit) { current_player->air_finished = level.time + 10_sec; - if (((current_client->pu_time_rebreather - level.time).milliseconds() % 2500) == 0) { - if (!current_client->breather_sound) - gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); - else - gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); - current_client->breather_sound ^= 1; - PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); - // FIXME: release a bubble? - } + if (((current_client->pu_time_rebreather - level.time).milliseconds() % 2500) == 0) { + if (!current_client->breather_sound) + gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath1.wav"), 1, ATTN_NORM, 0); + else + gi.sound(current_player, CHAN_AUTO, gi.soundindex("player/u_breath2.wav"), 1, ATTN_NORM, 0); + current_client->breather_sound ^= 1; + PlayerNoise(current_player, current_player->s.origin, PNOISE_SELF); + + vec3_t breath_origin = current_player->s.origin; + breath_origin[2] += current_player->viewheight; + vec3_t bubble_end = breath_origin + (up * 8); + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_BUBBLETRAIL); + gi.WritePosition(breath_origin); + gi.WritePosition(bubble_end); + gi.multicast(breath_origin, MULTICAST_PVS, false); + } } // if out of air, start drowning From 594ceda5ada4fd6603bff26e0cce93c665e9e14a Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:31:06 +0000 Subject: [PATCH 112/142] Propagate chase powerup effects --- src/g_chase.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/g_chase.cpp b/src/g_chase.cpp index db36486..8ee1c9e 100644 --- a/src/g_chase.cpp +++ b/src/g_chase.cpp @@ -81,6 +81,10 @@ void UpdateChaseCam(gentity_t *ent) { ent->client->ps.screen_blend = targ->client->ps.screen_blend; ent->client->ps.damage_blend = targ->client->ps.damage_blend; ent->client->ps.rdflags = targ->client->ps.rdflags; + std::copy(targ->client->ps.stats.begin() + STAT_POWERUP_INFO_START, targ->client->ps.stats.begin() + STAT_POWERUP_INFO_END + 1, ent->client->ps.stats.begin() + STAT_POWERUP_INFO_START); + ent->client->ps.stats[STAT_FLASHES] = targ->client->ps.stats[STAT_FLASHES]; + ent->client->ps.stats[STAT_POWERUP_ICON] = targ->client->ps.stats[STAT_POWERUP_ICON]; + ent->client->ps.stats[STAT_POWERUP_TIME] = targ->client->ps.stats[STAT_POWERUP_TIME]; ent->s.effects = targ->s.effects; ent->s.renderfx = targ->s.renderfx; From b3cb90f4935dc6106628ad6cbdc12f47476891ad Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:31:36 +0000 Subject: [PATCH 113/142] Reduce shambler explosion damage --- src/monsters/m_shambler.cpp | 63 +++++++++++++++++++++++++++++++- tests/shambler_balance_tests.cpp | 32 ++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 tests/shambler_balance_tests.cpp diff --git a/src/monsters/m_shambler.cpp b/src/monsters/m_shambler.cpp index c3283f9..73f3b6e 100644 --- a/src/monsters/m_shambler.cpp +++ b/src/monsters/m_shambler.cpp @@ -22,6 +22,57 @@ static cached_soundindex sound_melee2; static cached_soundindex sound_smack; static cached_soundindex sound_boom; +/* +============= +ShamblerIsExplosionMod + +Determines whether the provided mod represents explosion-style damage. +============= +*/ +static bool ShamblerIsExplosionMod(const mod_t &mod) +{ + switch (mod.id) { + case MOD_GRENADE: + case MOD_G_SPLASH: + case MOD_ROCKET: + case MOD_R_SPLASH: + case MOD_HANDGRENADE: + case MOD_HG_SPLASH: + case MOD_BFG_BLAST: + case MOD_BFG_EFFECT: + case MOD_EXPLOSIVE: + case MOD_BARREL: + case MOD_BOMB: + case MOD_SPLASH: + case MOD_RAILGUN_SPLASH: + return true; + + default: + return false; + } +} + +/* +============= +ShamblerApplyExplosionResistance + +Halves explosion damage, restoring the prevented amount to the shambler and +returning the scaled value for pain handling. +============= +*/ +int ShamblerApplyExplosionResistance(gentity_t *self, int damage, const mod_t &mod) +{ + if (!ShamblerIsExplosionMod(mod)) + return damage; + + const int reduced_damage = (damage + 1) / 2; + + if (damage > reduced_damage) + self->health += damage - reduced_damage; + + return reduced_damage; +} + // // misc // @@ -170,7 +221,6 @@ MONSTERINFO_RUN(shambler_run) (gentity_t *self) -> void { // pain // -// FIXME: needs halved explosion damage mframe_t shambler_frames_pain[] = { { ai_move }, @@ -182,14 +232,23 @@ mframe_t shambler_frames_pain[] = { }; MMOVE_T(shambler_move_pain) = { FRAME_pain01, FRAME_pain06, shambler_frames_pain, shambler_run }; +/* +============= +shambler_pain + +Handles shambler pain reactions with reduced explosion damage sensitivity. +============= +*/ static PAIN(shambler_pain) (gentity_t *self, gentity_t *other, float kick, int damage, const mod_t &mod) -> void { + const int adjusted_damage = ShamblerApplyExplosionResistance(self, damage, mod); + if (level.time < self->timestamp) return; self->timestamp = level.time + 1_ms; gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0); - if (mod.id != MOD_CHAINFIST && damage <= 30 && frandom() > 0.2f) + if (mod.id != MOD_CHAINFIST && adjusted_damage <= 30 && frandom() > 0.2f) return; // If hard or nightmare, don't go into pain while attacking diff --git a/tests/shambler_balance_tests.cpp b/tests/shambler_balance_tests.cpp new file mode 100644 index 0000000..3c66364 --- /dev/null +++ b/tests/shambler_balance_tests.cpp @@ -0,0 +1,32 @@ +#include "../src/g_local.h" +#include + +int ShamblerApplyExplosionResistance(gentity_t *self, int damage, const mod_t &mod); + +/* +============= +main + +Verifies explosion resistance halves splash damage for the shambler before pain handling. +============= +*/ +int main() +{ + gentity_t shambler{}; + shambler.health = 160; + + mod_t splash{ MOD_R_SPLASH }; + int adjusted = ShamblerApplyExplosionResistance(&shambler, 40, splash); + + assert(adjusted == 20); + assert(shambler.health == 180); + + shambler.health = 150; + mod_t direct{ MOD_SHOTGUN }; + adjusted = ShamblerApplyExplosionResistance(&shambler, 40, direct); + + assert(adjusted == 40); + assert(shambler.health == 150); + + return 0; +} From e0a4198088c118c4289112c14982d85981df6e3c Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:32:30 +0000 Subject: [PATCH 114/142] Handle monster spawning with arbitrary gravity --- src/g_local.h | 1 + src/g_monster_spawn.cpp | 84 ++++++++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 7daf342..87ec2ea 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -3232,6 +3232,7 @@ gentity_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop = true, const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); +bool SpawnCheckAndDropToFloor(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, const vec3_t &gravityVector = { 0.0f, 0.0f, -1.0f }); void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size); diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index 203dcb6..6de7c5b 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -148,6 +148,24 @@ bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t & // FIXME - all of this needs to be tweaked to handle the new gravity rules // if we ever want to spawn stuff on the roof +/* +============= +BoxExtentAlongDirection + +Returns the projection of the bounding box extents along the specified direction. +============= +*/ +static float BoxExtentAlongDirection(const vec3_t &mins, const vec3_t &maxs, const vec3_t &dir) +{ + vec3_t corner { + dir[0] >= 0.0f ? maxs[0] : mins[0], + dir[1] >= 0.0f ? maxs[1] : mins[1], + dir[2] >= 0.0f ? maxs[2] : mins[2] + }; + + return DotProduct(corner, dir); +} + /* ============= CheckSpawnPoint @@ -168,34 +186,29 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max if (!gravity_dir) gravity_dir = { 0.0f, 0.0f, -1.0f }; - vec3_t abs_gravity_dir = gravity_dir.abs(); - tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; - + if (tr.ent != world) return false; - - const float positive_extent = DotProduct(maxs, abs_gravity_dir); - const float negative_extent = Q_fabs(DotProduct(mins, abs_gravity_dir)); - - vec3_t upward_projection = gravity_dir * positive_extent; - vec3_t downward_projection = gravity_dir * negative_extent; - - if (positive_extent > 0.0f) + + const float along_gravity_extent = BoxExtentAlongDirection(mins, maxs, gravity_dir); + const float against_gravity_extent = BoxExtentAlongDirection(mins, maxs, -gravity_dir); + + if (against_gravity_extent > 0.0f) { - vec3_t upward_check = origin - upward_projection; - + vec3_t upward_check = origin - (gravity_dir * against_gravity_extent); + tr = gi.trace(origin, mins, maxs, upward_check, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; } - - if (negative_extent > 0.0f) + + if (along_gravity_extent > 0.0f) { - vec3_t downward_check = origin + downward_projection; - + vec3_t downward_check = origin + (gravity_dir * along_gravity_extent); + tr = gi.trace(origin, mins, maxs, downward_check, nullptr, MASK_MONSTERSOLID); if (tr.startsolid || tr.allsolid) return false; @@ -203,6 +216,43 @@ bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &max return true; } + +/* +============= +SpawnCheckAndDropToFloor + +Checks spawn clearance along the provided gravity vector and drops the origin to the nearest surface. +============= +*/ +bool SpawnCheckAndDropToFloor(vec3_t &origin, const vec3_t &mins, const vec3_t &maxs, const vec3_t &gravityVector) +{ + vec3_t gravity_dir = gravityVector.normalized(); + + if (!gravity_dir) + gravity_dir = { 0.0f, 0.0f, -1.0f }; + + const float along_gravity_extent = BoxExtentAlongDirection(mins, maxs, gravity_dir); + const float against_gravity_extent = BoxExtentAlongDirection(mins, maxs, -gravity_dir); + + if (!CheckSpawnPoint(origin, mins, maxs, gravity_dir)) + return false; + + vec3_t trace_start = origin - (gravity_dir * against_gravity_extent); + vec3_t trace_end = origin + (gravity_dir * (along_gravity_extent + against_gravity_extent + 256.0f)); + + trace_t tr = gi.trace(trace_start, mins, maxs, trace_end, nullptr, MASK_MONSTERSOLID); + + if (tr.startsolid || tr.allsolid) + return false; + + if (tr.fraction == 1.0f) + return false; + + origin = tr.endpos + (gravity_dir * against_gravity_extent); + + return CheckSpawnPoint(origin, mins, maxs, gravity_dir); +} + /* ============= CheckGroundSpawnPoint From 1894c8df694c264e06deb42ed4b90d8911a62a6b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:33:09 +0000 Subject: [PATCH 115/142] Cache entity string for gametype reset --- src/g_main.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index b06af49..80a627a 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -683,6 +683,51 @@ int gt_ctf = 0; int gt_g_gametype = 0; bool gt_teams_on = false; gametype_t gt_check = GT_NONE; +static char *gt_saved_entstring = nullptr; + +/* +============= +G_SaveGametypeEntityString + +Make a copy of the current level entity string so the level can be +rebuilt without a full map load. +============= +*/ +static bool G_SaveGametypeEntityString() { + if (gt_saved_entstring) { + gi.TagFree(gt_saved_entstring); + gt_saved_entstring = nullptr; + } + + if (level.entstring.empty()) + return false; + + size_t length = level.entstring.length() + 1; + gt_saved_entstring = (char *)gi.TagMalloc(length, TAG_GAME); + if (!gt_saved_entstring) + return false; + + Q_strlcpy(gt_saved_entstring, level.entstring.c_str(), length); + return true; +} + +/* +============= +G_LoadGametypeEntityString + +Reload the saved entity string and clear its cached copy. +============= +*/ +static bool G_LoadGametypeEntityString() { + if (!gt_saved_entstring) + return false; + + SpawnEntities(level.mapname, gt_saved_entstring, game.spawnpoint); + gi.TagFree(gt_saved_entstring); + gt_saved_entstring = nullptr; + return true; +} + void GT_Changes() { if (!deathmatch->integer) return; @@ -763,7 +808,8 @@ void GT_Changes() { return; //gi.Com_PrintFmt("GAMETYPE = {}\n", (int)gt); - + bool saved_entstring = G_SaveGametypeEntityString(); + if (gt_teams_on != Teams()) { team_reset = true; gt_teams_on = Teams(); @@ -803,14 +849,19 @@ void GT_Changes() { gt_check = (gametype_t)g_gametype->integer; } else return; - //TODO: save ent string so we can simply reload it and Match_Reset - //gi.AddCommandString("map_restart"); - - gi.AddCommandString(G_Fmt("gamemap {}\n", level.mapname).data()); - - GT_PrecacheAssets(); - GT_SetLongName(); - gi.LocBroadcast_Print(PRINT_CENTER, "{}", level.gametype_name); + if (saved_entstring && G_LoadGametypeEntityString()) { + Match_Reset(); + } else { + if (gt_saved_entstring) { + gi.TagFree(gt_saved_entstring); + gt_saved_entstring = nullptr; + } + gi.AddCommandString(G_Fmt("gamemap {}\n", level.mapname).data()); + } + + GT_PrecacheAssets(); + GT_SetLongName(); + gi.LocBroadcast_Print(PRINT_CENTER, "{}", level.gametype_name); } /* From f3b9862768637a5999ffa65cde52ff535d101361 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:34:11 +0000 Subject: [PATCH 116/142] Handle inverted spawn drops --- src/g_monster_spawn.cpp | 46 +++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/g_monster_spawn.cpp b/src/g_monster_spawn.cpp index 203dcb6..1ddf7d6 100644 --- a/src/g_monster_spawn.cpp +++ b/src/g_monster_spawn.cpp @@ -122,26 +122,50 @@ bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t & if (!gravity_dir) gravity_dir = { 0.0f, 0.0f, -1.0f }; - // drop first - if (!drop || !M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false)) + auto try_drop = [&] (vec3_t testpoint) -> bool { - spawnpoint = startpoint; + spawnpoint = testpoint; + + if (!CheckSpawnPoint(spawnpoint, mins, maxs, gravityVector)) + return false; + + if (!drop) + return true; + + if (M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false)) + return true; + + spawnpoint = testpoint; - // fix stuck if we couldn't drop initially if (G_FixStuckObject_Generic(spawnpoint, mins, maxs, [] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) { return gi.trace(start, mins, maxs, end, nullptr, MASK_MONSTERSOLID); - }) == stuck_result_t::NO_GOOD_POSITION) + }) != stuck_result_t::NO_GOOD_POSITION) { - spawnpoint = { 0.0f, 0.0f, 0.0f }; - return false; + return !drop || M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false); } - // fixed, so drop again - if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, gravity_dir, nullptr, MASK_MONSTERSOLID, false)) - return false; // ??? + spawnpoint = { 0.0f, 0.0f, 0.0f }; + return false; + }; + + // drop first + if (try_drop(startpoint)) + return true; + + vec3_t against_gravity = gravity_dir * -1.0f; + float move_amount = 16.0f; + + while (move_amount <= maxMoveUp) + { + vec3_t raised_start = startpoint + (against_gravity * move_amount); + + if (try_drop(raised_start)) + return true; + + move_amount += 16.0f; } - return true; + return false; } From 3ca1c7969555cb5fa90839a247754539296e4cd4 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:34:47 +0000 Subject: [PATCH 117/142] Load admin list for admin sessions --- src/g_local.h | 1 + src/g_main.cpp | 151 +++++++++++++++++++++++++++++++++++++++++------ src/p_client.cpp | 8 +-- 3 files changed, 136 insertions(+), 24 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 7daf342..0d5f325 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -3168,6 +3168,7 @@ void ChangeGametype(gametype_t gt); void GT_Changes(); void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint); void G_LoadMOTD(); +bool G_IsAdminSocialId(const char *social_id); // // g_chase.cpp diff --git a/src/g_main.cpp b/src/g_main.cpp index b06af49..3bae7a2 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -5,6 +5,10 @@ #include "bots/bot_includes.h" #include "monsters/m_player.h" // match starts #include +#include +#include +#include +#include CHECK_GCLIENT_INTEGRITY; CHECK_ENTITY_INTEGRITY; @@ -63,6 +67,8 @@ static cvar_t *maxentities; cvar_t *maxplayers; cvar_t *minplayers; +static std::unordered_set g_admin_social_ids; + cvar_t *ai_allow_dm_spawn; cvar_t *ai_damage_scale; cvar_t *ai_model_scale; @@ -619,37 +625,142 @@ static void InitGametype() { bool force_dm = false; if (g_gametype->integer < 0 || g_gametype->integer >= GT_NUM_GAMETYPES) - gi.cvar_forceset("g_gametype", G_Fmt("{}", clamp(g_gametype->integer, (int)GT_FIRST, (int)GT_LAST)).data()); - + gi.cvar_forceset("g_gametype", G_Fmt("{}", clamp(g_gametype->integer, (int)GT_FIRST, (int)GT_LAST)).data()); + if (ctf->integer) { - force_dm = true; - // force coop off - if (coop->integer) - gi.cvar_set(COOP, "0"); - // force tdm off - if (teamplay->integer) - gi.cvar_set("teamplay", "0"); + force_dm = true; + // force coop off + if (coop->integer) + gi.cvar_set(COOP, "0"); + // force tdm off + if (teamplay->integer) + gi.cvar_set("teamplay", "0"); } if (teamplay->integer) { - force_dm = true; - // force coop off - if (coop->integer) - gi.cvar_set(COOP, "0"); + force_dm = true; + // force coop off + if (coop->integer) + gi.cvar_set(COOP, "0"); } if (force_dm && !deathmatch->integer) { - gi.Com_Print("Forcing deathmatch.\n"); - gi.cvar_forceset("deathmatch", "1"); + gi.Com_Print("Forcing deathmatch.\n"); + gi.cvar_forceset("deathmatch", "1"); } // force even maxplayers value during teamplay if (Teams()) { - int pmax = maxplayers->integer; + int pmax = maxplayers->integer; - if (pmax != floor(pmax / 2)) - gi.cvar_set("maxplayers", G_Fmt("{}", floor(pmax / 2) * 2).data()); + if (pmax != floor(pmax / 2)) + gi.cvar_set("maxplayers", G_Fmt("{}", floor(pmax / 2) * 2).data()); + } + } + +/* +============= +G_Admins_NormalizeId + +Converts a social ID to lowercase for comparison. +============= +*/ +static std::string G_Admins_NormalizeId(const std::string &input) { + std::string normalized; + normalized.reserve(input.size()); + + for (char ch : input) + normalized.push_back((char)std::tolower((uint8_t)ch)); + + return normalized; + } + +/* +============= +G_Admins_TrimLine + +Strips comments and whitespace from a config line. +============= +*/ +static std::string G_Admins_TrimLine(const std::string &line) { + std::string trimmed = line; + + size_t comment = trimmed.find_first_of("#;"); + if (comment != std::string::npos) + trimmed = trimmed.substr(0, comment); + + size_t start = trimmed.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) + return {}; + + size_t end = trimmed.find_last_not_of(" \t\r\n"); + return trimmed.substr(start, end - start + 1); + } + +/* +============= +G_IsAdminSocialId + +Checks whether the provided social ID is in the admin list. +============= +*/ +bool G_IsAdminSocialId(const char *social_id) { + if (!social_id || !*social_id) + return false; + + std::string normalized = G_Admins_NormalizeId(social_id); + return g_admin_social_ids.find(normalized) != g_admin_social_ids.end(); + } + +/* +============= +G_LoadAdminList + +Loads admins from the admins.txt configuration file. +============= +*/ +static void G_LoadAdminList() { + g_admin_social_ids.clear(); + + const char *filename = "admins.txt"; + std::ifstream file(filename); + + if (!file.is_open()) { + gi.Com_PrintFmt("G_LoadAdminList: {} not found, skipping admin preload.\n", filename); + return; + } + + size_t line_number = 0; + std::string line; + + while (std::getline(file, line)) { + ++line_number; + + std::string trimmed = G_Admins_TrimLine(line); + if (trimmed.empty()) + continue; + + if (trimmed.size() >= MAX_INFO_VALUE) { + gi.Com_PrintFmt("G_LoadAdminList: line {} exceeds maximum length, ignoring.\n", line_number); + continue; + } + + if (trimmed.find_first_of(" \t") != std::string::npos) { + gi.Com_PrintFmt("G_LoadAdminList: unexpected whitespace on line {}, ignoring.\n", line_number); + continue; + } + + std::string normalized = G_Admins_NormalizeId(trimmed); + + if (normalized.empty()) { + gi.Com_PrintFmt("G_LoadAdminList: invalid entry on line {}, ignoring.\n", line_number); + continue; + } + + g_admin_social_ids.insert(normalized); + } + + gi.Com_PrintFmt("G_LoadAdminList: loaded {} admin entr{}.\n", g_admin_social_ids.size(), g_admin_social_ids.size() == 1 ? "y" : "ies"); } -} void ChangeGametype(gametype_t gt) { switch (gt) { @@ -1057,6 +1168,8 @@ g_dm_item_respawn_rate = gi.cvar("g_dm_item_respawn_rate", "1.0", CVAR_NOFLAGS); g_weapon_projection = gi.cvar("g_weapon_projection", "0", CVAR_NOFLAGS); g_weapon_respawn_time = gi.cvar("g_weapon_respawn_time", "30", CVAR_NOFLAGS); + G_LoadAdminList(); + bot_name_prefix = gi.cvar("bot_name_prefix", "B|", CVAR_NOFLAGS); // ruleset diff --git a/src/p_client.cpp b/src/p_client.cpp index 8485157..e6b627d 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -3794,11 +3794,9 @@ bool ClientConnect(gentity_t* ent, char* userinfo, const char* social_id, bool i // entity 1 is always server host, so make admin if (ent == &g_entities[1]) - ent->client->sess.admin = true; - else { - //TODO: check admins.txt for social_id - - } + ent->client->sess.admin = true; + else if (G_IsAdminSocialId(social_id)) + ent->client->sess.admin = true; // count current clients and rank for scoreboard CalculateRanks(); From 30a0b18b59e4b7b4dfe2f9a15b5647320577e2bc Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:35:22 +0000 Subject: [PATCH 118/142] Cache entity string for faster match resets --- src/g_cmds.cpp | 9 +++++---- src/g_local.h | 3 +++ src/g_main.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- src/g_spawn.cpp | 4 +++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/g_cmds.cpp b/src/g_cmds.cpp index 9695994..3aafdc7 100644 --- a/src/g_cmds.cpp +++ b/src/g_cmds.cpp @@ -3403,8 +3403,6 @@ static void Cmd_SetMap_f(gentity_t *ent) { ExitLevel(); } -extern void ClearWorldEntities(); - /* ============= Cmd_MapRestart_f @@ -3415,9 +3413,12 @@ Reset the match and world state before reloading the current map. static void Cmd_MapRestart_f(gentity_t *ent) { gi.Broadcast_Print(PRINT_HIGH, "[ADMIN]: Session reset.\n"); + G_SaveLevelEntstring(); Match_Reset(); - ClearWorldEntities(); - SpawnEntities(level.mapname, level.entstring.c_str(), nullptr); + + if (G_ResetLevelFromSavedEntstring()) + return; + gi.AddCommandString(G_Fmt("gamemap {}\n", level.mapname).data()); } diff --git a/src/g_local.h b/src/g_local.h index 7daf342..3180ce1 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -3167,6 +3167,9 @@ bool InAMatch(); void ChangeGametype(gametype_t gt); void GT_Changes(); void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint); +void ClearWorldEntities(); +void G_SaveLevelEntstring(); +bool G_ResetLevelFromSavedEntstring(); void G_LoadMOTD(); // diff --git a/src/g_main.cpp b/src/g_main.cpp index b06af49..5590db1 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -34,6 +34,8 @@ gentity_t *g_entities; cvar_t *hostname; +static std::string saved_level_entstring; + cvar_t *deathmatch; cvar_t *ctf; cvar_t *teamplay; @@ -803,9 +805,7 @@ void GT_Changes() { gt_check = (gametype_t)g_gametype->integer; } else return; - //TODO: save ent string so we can simply reload it and Match_Reset - //gi.AddCommandString("map_restart"); - + G_SaveLevelEntstring(); gi.AddCommandString(G_Fmt("gamemap {}\n", level.mapname).data()); GT_PrecacheAssets(); @@ -813,6 +813,42 @@ void GT_Changes() { gi.LocBroadcast_Print(PRINT_CENTER, "{}", level.gametype_name); } +/* +============= +G_SaveLevelEntstring + +Preserve the currently loaded entity string so it can be reused when rebuilding the level without a full map reload. +============= +*/ +void G_SaveLevelEntstring() { + if (!level.entstring.empty()) + saved_level_entstring = level.entstring; +} + +/* +============= +G_ResetLevelFromSavedEntstring + +Rebuild the level using the cached entity string if available, avoiding a full map reload. Returns true when the reset completed using the cached data. +============= +*/ +bool G_ResetLevelFromSavedEntstring() { + const char *entities = nullptr; + + if (!saved_level_entstring.empty()) + entities = saved_level_entstring.c_str(); + else if (!level.entstring.empty()) + entities = level.entstring.c_str(); + + if (!entities) + return false; + + ClearWorldEntities(); + SpawnEntities(level.mapname, entities, nullptr); + + return true; +} + /* ============ PreInitGame diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index a1cfe7c..6c18c04 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1572,7 +1572,7 @@ static void G_LocateSpawnSpots(void) { /* ============= -ParseWorldEntityString + ParseWorldEntityString Loads the base entity string for the level and optionally overrides it with an external .ent file. @@ -1870,6 +1870,8 @@ void SpawnEntities(const char *mapname, const char *entities, const char *spawnp level.entstring = new_entstring; ParseWorldEntityString(mapname, RS(RS_Q3A)); + G_SaveLevelEntstring(); + level.is_n64 = strncmp(level.mapname, "q64/", 4) == 0; level.coop_scale_players = 0; From 1e6d1c537d187f3df2bec5589185162e4531a198 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:36:07 +0000 Subject: [PATCH 119/142] Improve medic reinforcement selection fairness --- src/monsters/m_medic.cpp | 59 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/monsters/m_medic.cpp b/src/monsters/m_medic.cpp index 5dc0827..2e130d8 100644 --- a/src/monsters/m_medic.cpp +++ b/src/monsters/m_medic.cpp @@ -11,6 +11,7 @@ MEDIC #include "../g_local.h" #include "m_medic.h" #include "m_flash.h" +#include constexpr float MEDIC_MIN_DISTANCE = 32; constexpr float MEDIC_MAX_HEAL_DISTANCE = 400; @@ -66,6 +67,15 @@ constexpr std::array reinforcement_position = { vec3_t { 0, -80, 0 } }; +static uint8_t next_reinforcement_cursor; + +/* +============= +M_PickValidReinforcements + +Filters the reinforcements that can fit within the available space. +============= +*/ // filter out the reinforcement indices we can pick given the space we have left static void M_PickValidReinforcements(gentity_t *self, int32_t space, std::vector &output) { output.clear(); @@ -76,6 +86,14 @@ static void M_PickValidReinforcements(gentity_t *self, int32_t space, std::vecto } // pick an array of reinforcements to use; note that this does not modify `self` +/* +============= +M_PickReinforcements + +Picks reinforcements using a round-robin cursor and avoids immediate repetition +when alternatives are available. +============= +*/ std::array M_PickReinforcements(gentity_t *self, int32_t &num_chosen, int32_t max_slots = 0) { static std::vector available; std::array chosen; @@ -88,6 +106,8 @@ std::array M_PickReinforcements(gentity_t *self, in // we only have this many slots left to use int32_t remaining = self->monsterinfo.monster_slots - self->monsterinfo.monster_used; + std::vector used_this_pick; + uint8_t last_choice = 255; for (num_chosen = 0; num_chosen < num_slots; num_chosen++) { // ran out of slots! @@ -101,12 +121,41 @@ std::array M_PickReinforcements(gentity_t *self, in if (!available.size()) break; - // select monster, TODO fairly - chosen[num_chosen] = random_element(available); + uint8_t choice = 255; + size_t start = next_reinforcement_cursor % available.size(); + + for (size_t offset = 0; offset < available.size(); offset++) { + const uint8_t candidate = available[(start + offset) % available.size()]; + + if (available.size() > 1 && candidate == last_choice) + continue; + + if (std::find(used_this_pick.begin(), used_this_pick.end(), candidate) != used_this_pick.end()) + continue; + + choice = candidate; + break; + } + + if (choice == 255) + choice = available[start]; + + next_reinforcement_cursor = (next_reinforcement_cursor + 1) % max(self->monsterinfo.reinforcements.num_reinforcements, 1); + last_choice = choice; + used_this_pick.push_back(choice); + chosen[num_chosen] = choice; remaining -= self->monsterinfo.reinforcements.reinforcements[chosen[num_chosen]].strength; } + if (developer->integer) { + gi.dprintf("[Medic] Reinforcement picks (slots %d used %d):", self->monsterinfo.monster_slots, self->monsterinfo.monster_used); + for (int32_t i = 0; i < num_chosen; i++) { + gi.dprintf(" %d", chosen[i]); + } + gi.dprintf("\n"); + } + return chosen; } @@ -136,7 +185,7 @@ void M_SetupReinforcements(const char *reinforcements, reinforcement_list_t &lis const char *token = COM_ParseEx(&p, "; "); if (!*token || r == list.reinforcements + list.num_reinforcements) - break; + break; r->classname = G_CopyString(token, TAG_LEVEL); @@ -1115,7 +1164,7 @@ static void medic_spawngrows(gentity_t *self) { for (size_t i = 0; i < MAX_REINFORCEMENTS; i++, num_summoned++) if (self->monsterinfo.chosen_reinforcements[i] == 255) - break; + break; for (count = 0; count < num_summoned; count++) { offset = reinforcement_position[count]; @@ -1153,7 +1202,7 @@ static void medic_finish_spawn(gentity_t *self) { for (size_t i = 0; i < MAX_REINFORCEMENTS; i++, num_summoned++) if (self->monsterinfo.chosen_reinforcements[i] == 255) - break; + break; for (count = 0; count < num_summoned; count++) { auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]]; From a9d8993ccd5293e620a470ad70126a9e7f033c08 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:36:46 +0000 Subject: [PATCH 120/142] Refactor findradius midpoint calculation --- src/g_utils.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index fb50b1d..4b64dbc 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -45,8 +45,9 @@ findradius (origin, radius) ================= */ gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { - vec3_t eorg; - int j; + const auto compute_center = [](const gentity_t* ent) { + return ent->s.origin + (ent->mins + ent->maxs) * 0.5f; + }; if (!from) from = g_entities; @@ -57,8 +58,7 @@ gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { continue; if (from->solid == SOLID_NOT) continue; - for (j = 0; j < 3; j++) - eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f); + const vec3_t eorg = org - compute_center(from); if (eorg.length() > rad) continue; return from; @@ -68,6 +68,7 @@ gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { } + /* ============= G_PickTarget From da8cd045834ccd706550575396441f2c7a7e8e66 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:37:25 +0000 Subject: [PATCH 121/142] Add engine version metadata to game saves --- src/g_save.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 14 deletions(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index 2292f75..adaddd4 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -24,6 +24,22 @@ // does have some C-isms in here. constexpr size_t SAVE_FORMAT_VERSION = 1; +/* +============= +GetEngineBuildString + +Fetches the engine build string from the version cvar. +============= +*/ +static const char *GetEngineBuildString() { + cvar_t *engine_version = gi.cvar("version", "", 0); + + if (!engine_version || !engine_version->string) + return ""; + + return engine_version->string; +} + #include #include #include @@ -2361,6 +2377,13 @@ void read_save_struct_json(const Json::Value &json, void *data, const save_struc #include #include +/* +============= +parseJson + +Parses a JSON string into a Json::Value and errors on failure. +============= +*/ static Json::Value parseJson(const char *jsonString) { Json::CharReaderBuilder reader; reader["allowSpecialFloats"] = true; @@ -2377,12 +2400,19 @@ static Json::Value parseJson(const char *jsonString) { return json; } +/* +============= +saveJson + +Serializes a Json::Value to a TagMalloc'ed string buffer. +============= +*/ static char *saveJson(const Json::Value &json, size_t *out_size) { Json::StreamWriterBuilder builder; builder["indentation"] = "\t"; builder["useSpecialFloats"] = true; const std::unique_ptr writer(builder.newStreamWriter()); - std::stringstream ss(std::ios_base::out | std::ios_base::binary); + std::stringstream ss(std::ios_base::out | std::ios_base::binary); writer->write(json, &ss); *out_size = ss.tellp(); char *const out = static_cast(gi.TagMalloc(*out_size + 1, TAG_GAME)); @@ -2393,8 +2423,13 @@ static char *saveJson(const Json::Value &json, size_t *out_size) { return out; } -// new entry point for WriteGame. -// returns pointer to TagMalloc'd JSON string. +/* +============= +WriteGameJson + +Serializes the current game state to TagMalloc'd JSON data. +============= +*/ char *WriteGameJson(bool autosave, size_t *out_size) { if (!autosave) SaveClientData(); @@ -2402,7 +2437,7 @@ char *WriteGameJson(bool autosave, size_t *out_size) { Json::Value json(Json::objectValue); json["save_version"] = SAVE_FORMAT_VERSION; - // TODO: engine version ID? + json["engine_version"] = GetEngineBuildString(); // write game game.autosaved = autosave; @@ -2423,13 +2458,51 @@ char *WriteGameJson(bool autosave, size_t *out_size) { void PrecacheInventoryItems(); -// new entry point for ReadGame. -// takes in pointer to JSON data. does -// not store or modify it. +/* +============= +ValidateEngineVersion + +Validates the engine version recorded in the save JSON and warns or aborts on mismatches. +============= +*/ +static void ValidateEngineVersion(const Json::Value &json) { + const Json::Value &engine_version_json = json["engine_version"]; + const char *current_engine_version = GetEngineBuildString(); + + if (!engine_version_json.isString()) { + if (g_strict_saves->integer) + gi.Com_Error("expected \"engine_version\" to be string"); + else + gi.Com_Print("warning: save file missing engine version info; continuing load anyway.\n"); + + return; + } + + const std::string save_engine_version = engine_version_json.asString(); + const char *actual_engine_version = (current_engine_version && current_engine_version[0]) ? current_engine_version : ""; + + if (save_engine_version != actual_engine_version) { + const std::string warning = fmt::format("Save was created with engine version \"{}\" but current engine is \"{}\".", save_engine_version, actual_engine_version); + + if (g_strict_saves->integer) + gi.Com_Error(warning.c_str()); + else + gi.Com_PrintFmt("{}\n", warning); + } +} + +/* +============= +ReadGameJson + +Loads game state from JSON data and restores entities and clients. +============= +*/ void ReadGameJson(const char *jsonString) { gi.FreeTags(TAG_GAME); Json::Value json = parseJson(jsonString); + ValidateEngineVersion(json); uint32_t max_entities = game.maxentities; uint32_t max_clients = game.maxclients; @@ -2463,8 +2536,13 @@ void ReadGameJson(const char *jsonString) { PrecacheInventoryItems(); } -// new entry point for WriteLevel. -// returns pointer to TagMalloc'd JSON string. +/* +============= +WriteLevelJson + +Serializes the current level state to TagMalloc'd JSON data. +============= +*/ char *WriteLevelJson(bool transition, size_t *out_size) { // update current level entry now, just so we can // use gamemap to test EOU @@ -2479,7 +2557,7 @@ char *WriteLevelJson(bool transition, size_t *out_size) { // write entities Json::Value entities(Json::objectValue); - char number[16]; + char number[16]; for (size_t i = 0; i < globals.num_entities; i++) { if (!globals.gentities[i].inuse) @@ -2505,9 +2583,13 @@ char *WriteLevelJson(bool transition, size_t *out_size) { return saveJson(json, out_size); } -// new entry point for ReadLevel. -// takes in pointer to JSON data. does -// not store or modify it. +/* +============= +ReadLevelJson + +Loads level state from JSON data and repopulates entities. +============= +*/ void ReadLevelJson(const char *jsonString) { // free any dynamic memory allocated by loading the level // base state @@ -2535,7 +2617,7 @@ void ReadLevelJson(const char *jsonString) { const char *dummy; const char *id = it.memberName(&dummy); const Json::Value &value = *it;//json[key]; - uint32_t number = strtoul(id, nullptr, 10); + uint32_t number = strtoul(id, nullptr, 10); if (number >= globals.num_entities) globals.num_entities = number + 1; From 04ffca356809055a5ccc80794a79762641ef81f7 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 20:38:08 +0000 Subject: [PATCH 122/142] Refactor stuck position selection --- src/p_move.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/p_move.cpp b/src/p_move.cpp index c53eb8d..2d19407 100644 --- a/src/p_move.cpp +++ b/src/p_move.cpp @@ -3,6 +3,9 @@ #include "q_std.h" +#include +#include + #define GAME_INCLUDE #include "bg_local.h" @@ -21,11 +24,12 @@ stuck_result_t G_FixStuckObject_Generic(vec3_t& origin, const vec3_t& own_mins, if (!trace(origin, own_mins, own_maxs, origin).startsolid) return stuck_result_t::GOOD_POSITION; - struct { + struct GoodPosition { float distance; vec3_t origin; - } good_positions[6]; - size_t num_good_positions = 0; + }; + std::vector good_positions; + good_positions.reserve(q_countof(side_checks)); constexpr struct { std::array normal; @@ -138,18 +142,16 @@ stuck_result_t G_FixStuckObject_Generic(vec3_t& origin, const vec3_t& own_mins, if (tr.startsolid) continue; - good_positions[num_good_positions].origin = new_origin; - good_positions[num_good_positions].distance = delta.lengthSquared(); - num_good_positions++; + good_positions.emplace_back(GoodPosition{ delta.lengthSquared(), new_origin }); } - if (num_good_positions) { - std::sort(&good_positions[0], &good_positions[0] + num_good_positions, [](const auto& a, const auto& b) { return a.distance < b.distance; }); + if (!good_positions.empty()) { + const auto best = std::ranges::min_element(good_positions, [](const auto& a, const auto& b) { return a.distance < b.distance; }); - origin = good_positions[0].origin; + origin = best->origin; return stuck_result_t::FIXED; -} + } return stuck_result_t::NO_GOOD_POSITION; } From 7c8eaae7393b1be9e018652a3fde07da38c59392 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 21:07:59 +0000 Subject: [PATCH 123/142] Add Horde life tracking and limits --- src/g_local.h | 11 +++++ src/g_main.cpp | 95 ++++++++++++++++++++++++++++++++----- src/g_spawn.cpp | 5 +- src/p_client.cpp | 92 ++++++++++++++++++++++++++++------- src/p_hud.cpp | 2 +- tests/horde_lives_tests.cpp | 88 ++++++++++++++++++++++++++++++++++ 6 files changed, 262 insertions(+), 31 deletions(-) create mode 100644 tests/horde_lives_tests.cpp diff --git a/src/g_local.h b/src/g_local.h index 7daf342..704a45c 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -2394,6 +2394,7 @@ extern cvar_t *g_coop_health_scaling; extern cvar_t *g_coop_instanced_items; extern cvar_t *g_coop_num_lives; extern cvar_t *g_coop_player_collision; +extern cvar_t *g_horde_num_lives; extern cvar_t *g_coop_squad_respawn; extern cvar_t *g_corpse_sink_time; extern cvar_t *g_damage_scale; @@ -3009,6 +3010,16 @@ void ClientBeginServerFrame(gentity_t *ent); void ClientUserinfoChanged(gentity_t *ent, const char *userinfo); void Match_Ghost_Assign(gentity_t *ent); void Match_Ghost_DoAssign(gentity_t *ent); + +struct player_life_state_t { + bool playing; + bool eliminated; + int32_t health; + int32_t lives; +}; + +bool Horde_LivesEnabled(); +bool Horde_NoLivesRemain(const std::vector &states); void P_AssignClientSkinnum(gentity_t *ent); void P_ForceFogTransition(gentity_t *ent, bool instant); void P_SendLevelPOI(gentity_t *ent); diff --git a/src/g_main.cpp b/src/g_main.cpp index b06af49..b91492b 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -99,6 +99,7 @@ cvar_t *g_coop_health_scaling; cvar_t *g_coop_instanced_items; cvar_t *g_coop_num_lives; cvar_t *g_coop_player_collision; +cvar_t *g_horde_num_lives; cvar_t *g_coop_squad_respawn; cvar_t *g_corpse_sink_time; cvar_t *g_damage_scale; @@ -530,6 +531,13 @@ static void Horde_Init() { */ } +/* +============= +Horde_AllMonstersDead + +Returns true when no live monsters remain in the level. +============= +*/ static bool Horde_AllMonstersDead() { for (size_t i = 0; i < globals.max_entities; i++) { if (!g_entities[i].inuse) @@ -543,6 +551,42 @@ static bool Horde_AllMonstersDead() { return true; } +/* +============= +Horde_LivesEnabled + +Returns true when Horde is configured to use limited lives. +============= +*/ +bool Horde_LivesEnabled() { + return GT(GT_HORDE) && g_horde_num_lives->integer > 0; +} + +/* +============= +Horde_NoLivesRemain + +Returns true when every playing client is out of lives or eliminated. +============= +*/ +bool Horde_NoLivesRemain(const std::vector &states) { + bool have_players = false; + + for (const auto &state : states) { + if (!state.playing) + continue; + + have_players = true; + + if (state.health > 0) + return false; + + if (!state.eliminated && state.lives > 0) + return false; + } + + return have_players; +} // ================================================= @@ -890,10 +934,11 @@ static void InitGame() { // [Paril-KEX] g_coop_player_collision = gi.cvar("g_coop_player_collision", "0", CVAR_LATCH); - g_coop_squad_respawn = gi.cvar("g_coop_squad_respawn", "1", CVAR_LATCH); - g_coop_enable_lives = gi.cvar("g_coop_enable_lives", "0", CVAR_LATCH); - g_coop_num_lives = gi.cvar("g_coop_num_lives", "2", CVAR_LATCH); - g_coop_instanced_items = gi.cvar("g_coop_instanced_items", "1", CVAR_LATCH); +g_coop_squad_respawn = gi.cvar("g_coop_squad_respawn", "1", CVAR_LATCH); +g_coop_enable_lives = gi.cvar("g_coop_enable_lives", "0", CVAR_LATCH); +g_coop_num_lives = gi.cvar("g_coop_num_lives", "2", CVAR_LATCH); +g_horde_num_lives = gi.cvar("g_horde_num_lives", "0", CVAR_SERVERINFO | CVAR_LATCH); +g_coop_instanced_items = gi.cvar("g_coop_instanced_items", "1", CVAR_LATCH); g_allow_grapple = gi.cvar("g_allow_grapple", "auto", CVAR_NOFLAGS); g_allow_kill = gi.cvar("g_allow_kill", "1", CVAR_NOFLAGS); g_grapple_offhand = gi.cvar("g_grapple_offhand", "0", CVAR_NOFLAGS); @@ -1860,9 +1905,21 @@ static void CheckDMRoundState(void) { Round_End(); return; } - break; - } +break; +} case GT_HORDE: + if (Horde_LivesEnabled()) { + std::vector life_states; + + for (auto ec : active_clients()) + life_states.push_back({ ClientIsPlaying(ec->client), ec->client->eliminated, ec->health, ec->client->pers.lives }); + + if (Horde_NoLivesRemain(life_states)) { + gi.LocBroadcast_Print(PRINT_CENTER, "No lives remaining!\n"); + QueueIntermission("OUT OF LIVES", true, false); + return; + } + } Horde_RunSpawning(); //if (level.horde_all_spawned && Horde_AllMonstersDead()) { if (level.horde_all_spawned && !(level.total_monsters - level.killed_monsters)) { @@ -3669,7 +3726,17 @@ static void CheckCvars() { CheckMinMaxPlayers(); } -static bool G_AnyDeadPlayersWithoutLives() { +/* +============= +G_AnyDeadPlayersWithoutLives + +Checks for any dead players who have exhausted their lives and should remain eliminated. +============= +*/ +static bool G_AnyDeadPlayersWithoutLives(bool limited_lives) { + if (!limited_lives) + return false; + for (auto player : active_clients()) if (player->health <= 0 && (!player->client->pers.lives || player->client->eliminated)) return true; @@ -3677,6 +3744,8 @@ static bool G_AnyDeadPlayersWithoutLives() { return false; } + + /* ================ CheckDMEndFrame @@ -3773,18 +3842,20 @@ static inline void G_RunFrame_(bool main_loop) { // clear client coop respawn states; this is done // early since it may be set multiple times for different // players - if (InCoopStyle() && (g_coop_enable_lives->integer || g_coop_squad_respawn->integer)) { + if (InCoopStyle() && (g_coop_squad_respawn->integer || g_coop_enable_lives->integer || Horde_LivesEnabled())) { + const bool limited_lives = g_coop_enable_lives->integer || Horde_LivesEnabled(); + for (auto player : active_clients()) { if (player->client->respawn_time >= level.time) player->client->coop_respawn_state = COOP_RESPAWN_WAITING; - else if (g_coop_enable_lives->integer && player->health <= 0 && player->client->pers.lives == 0) + else if (limited_lives && player->health <= 0 && player->client->pers.lives == 0) player->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; - else if (g_coop_enable_lives->integer && G_AnyDeadPlayersWithoutLives()) + else if (G_AnyDeadPlayersWithoutLives(limited_lives)) player->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; else player->client->coop_respawn_state = COOP_RESPAWN_NONE; - } - } +} +} // // treat each object in turn diff --git a/src/g_spawn.cpp b/src/g_spawn.cpp index a1cfe7c..25ea491 100644 --- a/src/g_spawn.cpp +++ b/src/g_spawn.cpp @@ -1979,8 +1979,9 @@ static void G_InitStatusbar() { // top of screen coop respawn display sb.ifstat(STAT_COOP_RESPAWN).xv(0).yt(0).loc_stat_cstring2(STAT_COOP_RESPAWN).endifstat(); - // coop lives - if (g_coop_enable_lives->integer && g_coop_num_lives->integer > 0) + // coop & horde lives +const bool limited_lives = (g_coop_enable_lives->integer && g_coop_num_lives->integer > 0) || Horde_LivesEnabled(); + if (limited_lives) sb.ifstat(STAT_LIVES).xr(-16).yt(y = 2).lives_num(STAT_LIVES).xr(0).yt(y += text_adj).loc_rstring("$g_lives").endifstat(); // total monsters diff --git a/src/p_client.cpp b/src/p_client.cpp index 8485157..524c617 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -979,6 +979,23 @@ static bool Match_CanScore() { return true; } +/* +============= +GetConfiguredLives + +Returns the configured life count for coop-style modes, including Horde. +============= +*/ +static int GetConfiguredLives() { + if (g_coop_enable_lives->integer) + return g_coop_num_lives->integer + 1; + + if (Horde_LivesEnabled()) + return g_horde_num_lives->integer + 1; + + return 0; +} + /* ================== player_die @@ -1077,6 +1094,40 @@ DIE(player_die) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int } } + const bool limited_lives = g_coop_enable_lives->integer || Horde_LivesEnabled(); + const bool squad_respawn = coop->integer && g_coop_squad_respawn->integer; + + if (InCoopStyle() && (squad_respawn || limited_lives)) { + if (limited_lives && self->client->pers.lives) { + self->client->pers.lives--; + if (g_coop_enable_lives->integer) + self->client->resp.coop_respawn.lives--; + } + + bool allPlayersDead = true; + + for (auto player : active_clients()) + if (player->health > 0 || (!level.deadly_kill_box && limited_lives && player->client->pers.lives > 0)) { + allPlayersDead = false; + break; + } + + if (allPlayersDead) { // allow respawns for telefrags and weird shit + level.coop_level_restart_time = level.time + 5_sec; + + for (auto player : active_clients()) + gi.LocCenter_Print(player, "$g_coop_lose"); + } + + if (limited_lives && !self->client->pers.lives) + self->client->eliminated = true; + + // in 3 seconds, attempt a respawn or put us into + // spectator mode + if (!level.coop_level_restart_time) + self->client->respawn_time = level.time + 3_sec; + } + LookAtKiller(self, inflictor, attacker); self->client->ps.pmove.pm_type = PM_DEAD; ClientObituary(self, inflictor, attacker, mod); @@ -1216,22 +1267,24 @@ DIE(player_die) (gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int } if (!self->deadflag) { - if (InCoopStyle() && (g_coop_squad_respawn->integer || g_coop_enable_lives->integer)) { - if (g_coop_enable_lives->integer && self->client->pers.lives) { + const bool limited_lives = g_coop_enable_lives->integer || Horde_LivesEnabled(); + + if (InCoopStyle() && (g_coop_squad_respawn->integer || limited_lives)) { + if (limited_lives && self->client->pers.lives) { self->client->pers.lives--; - self->client->resp.coop_respawn.lives--; + if (g_coop_enable_lives->integer) + self->client->resp.coop_respawn.lives--; } bool allPlayersDead = true; for (auto player : active_clients()) - if (player->health > 0 || (!level.deadly_kill_box && g_coop_enable_lives->integer && player->client->pers.lives > 0)) { + if (player->health > 0 || (!level.deadly_kill_box && limited_lives && player->client->pers.lives > 0)) { allPlayersDead = false; break; } - if (allPlayersDead) // allow respawns for telefrags and weird shit - { + if (allPlayersDead) { // allow respawns for telefrags and weird shit level.coop_level_restart_time = level.time + 5_sec; for (auto player : active_clients()) @@ -1522,8 +1575,11 @@ void InitClientPersistant(gentity_t* ent, gclient_t* client) { client->pers.lastweapon = client->pers.weapon; } - if (InCoopStyle() && g_coop_enable_lives->integer) - client->pers.lives = g_coop_num_lives->integer + 1; + if (InCoopStyle()) { + int configured_lives = GetConfiguredLives(); + if (configured_lives) + client->pers.lives = configured_lives; + } if (ent->client->pers.autoshield >= AUTO_SHIELD_AUTO) ent->flags |= FL_WANTS_POWER_ARMOR; @@ -2773,8 +2829,11 @@ void ClientSpawn(gentity_t* ent) { ClientSetEliminated(ent); bool eliminated = ent->client->eliminated; int lives = 0; - if (InCoopStyle() && g_coop_enable_lives->integer) - lives = ent->client->pers.spawned ? ent->client->pers.lives : g_coop_enable_lives->integer + 1; + if (InCoopStyle()) { + const int configured_lives = GetConfiguredLives(); + if (configured_lives) + lives = ent->client->pers.spawned ? ent->client->pers.lives : configured_lives; + } // clear velocity now, since landmark may change it ent->velocity = {}; @@ -4671,18 +4730,19 @@ static bool G_CoopRespawn(gentity_t* ent) { // don't do this in non-coop if (!InCoopStyle()) return false; + + const bool limited_lives = g_coop_enable_lives->integer || Horde_LivesEnabled(); // if we don't have squad or lives, it doesn't matter - if (!g_coop_squad_respawn->integer && !g_coop_enable_lives->integer) + if (!g_coop_squad_respawn->integer && !limited_lives) return false; respawn_state_t state = RESPAWN_NONE; // first pass: if we have no lives left, just move to spectator - if (g_coop_enable_lives->integer) { - if (ent->client->pers.lives == 0) { - state = RESPAWN_SPECTATE; - ent->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; - } + if (limited_lives && ent->client->pers.lives == 0) { + state = RESPAWN_SPECTATE; + ent->client->coop_respawn_state = COOP_RESPAWN_NO_LIVES; + ClientSetEliminated(ent); } // second pass: check for where to spawn diff --git a/src/p_hud.cpp b/src/p_hud.cpp index 90434c3..e0c9fbd 100644 --- a/src/p_hud.cpp +++ b/src/p_hud.cpp @@ -1125,7 +1125,7 @@ void Cmd_Help_f(gentity_t *ent) { // even if we're spectating void G_SetCoopStats(gentity_t *ent) { - if (InCoopStyle() && g_coop_enable_lives->integer) + if (InCoopStyle() && (g_coop_enable_lives->integer || Horde_LivesEnabled())) ent->client->ps.stats[STAT_LIVES] = ent->client->pers.lives + 1; else ent->client->ps.stats[STAT_LIVES] = 0; diff --git a/tests/horde_lives_tests.cpp b/tests/horde_lives_tests.cpp new file mode 100644 index 0000000..ccf69b9 --- /dev/null +++ b/tests/horde_lives_tests.cpp @@ -0,0 +1,88 @@ +#include "../src/g_local.h" +#include +#include + +static int g_failures = 0; + +/* +============= +Expect + +Reports a failed expectation and increments the failure counter. +============= +*/ +static void Expect(bool condition, const char *message) +{ + if (!condition) + { + std::fprintf(stderr, "Expectation failed: %s\n", message); + ++g_failures; + } +} + +/* +============= +TestAlivePlayerPreventsElimination + +Verifies that a living player keeps the wave active regardless of lives. +============= +*/ +static void TestAlivePlayerPreventsElimination() +{ + std::vector states = { + { true, false, 100, 0 }, + { true, false, -10, 0 } + }; + + Expect(!Horde_NoLivesRemain(states), "Living players should block elimination"); +} + +/* +============= +TestRemainingLivesPreventFailure + +Ensures remaining lives keep players eligible to respawn. +============= +*/ +static void TestRemainingLivesPreventFailure() +{ + std::vector states = { + { true, false, -20, 2 }, + { true, false, -5, 0 } + }; + + Expect(!Horde_NoLivesRemain(states), "Remaining lives should prevent elimination"); +} + +/* +============= +TestNoLivesTriggersElimination + +Confirms that exhausted lives across the roster force elimination. +============= +*/ +static void TestNoLivesTriggersElimination() +{ + std::vector states = { + { true, false, -10, 0 }, + { true, true, -15, 0 } + }; + + Expect(Horde_NoLivesRemain(states), "No lives should trigger elimination"); +} + +/* +============= +main + +Runs Horde life exhaustion regression checks. +============= +*/ +int main() +{ + TestAlivePlayerPreventsElimination(); + TestRemainingLivesPreventFailure(); + TestNoLivesTriggersElimination(); + + return g_failures == 0 ? 0 : 1; +} From ddcddfab29c87e3bf166cf65155a350a13c48d17 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 21:09:24 +0000 Subject: [PATCH 124/142] Track CTF flag objective states --- src/bots/bot_utils.cpp | 37 ++++- src/game.h | 21 ++- tests/ctf_flag_state_tests.cpp | 263 +++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 tests/ctf_flag_state_tests.cpp diff --git a/src/bots/bot_utils.cpp b/src/bots/bot_utils.cpp index 8c7048a..c6764a2 100644 --- a/src/bots/bot_utils.cpp +++ b/src/bots/bot_utils.cpp @@ -16,6 +16,7 @@ static void Player_UpdateState(gentity_t *player) { const client_persistant_t &persistant = player->client->pers; player->sv.ent_flags = SVFL_NONE; + player->sv.objective_state = objective_state_t::None; if (player->groundentity != nullptr || (player->flags & FL_PARTIALGROUND) != 0) { player->sv.ent_flags |= SVFL_ONGROUND; } else { @@ -171,6 +172,7 @@ Monster_UpdateState */ static void Monster_UpdateState(gentity_t *monster) { monster->sv.ent_flags = SVFL_NONE; + monster->sv.objective_state = objective_state_t::None; if (monster->groundentity != nullptr) { monster->sv.ent_flags |= SVFL_ONGROUND; } @@ -229,6 +231,7 @@ Item_UpdateState static void Item_UpdateState(gentity_t *item) { item->sv.ent_flags = SVFL_IS_ITEM; item->sv.respawntime = 0; + item->sv.objective_state = objective_state_t::None; if (item->team != nullptr) { item->sv.ent_flags |= SVFL_IN_TEAM; @@ -251,7 +254,37 @@ static void Item_UpdateState(gentity_t *item) { const item_id_t itemID = item->item->id; if (itemID == IT_FLAG_RED || itemID == IT_FLAG_BLUE) { item->sv.ent_flags |= SVFL_IS_OBJECTIVE; - // TODO: figure out if the objective is dropped/carried/home... + item->sv.team = (itemID == IT_FLAG_RED) ? TEAM_RED : TEAM_BLUE; + + if (!item->sv.init) { + item->sv.start_origin = item->s.origin; + } + + item->sv.end_origin = item->s.origin; + + const bool isDroppedFlag = item->spawnflags.has(SPAWNFLAG_ITEM_DROPPED) || item->owner != nullptr; + const bool isHiddenFlag = (item->solid == SOLID_NOT) || ((item->svflags & SVF_NOCLIENT) != 0); + const bool isRespawning = ((item->svflags & SVF_RESPAWNING) != 0) || ((item->flags & FL_RESPAWN) != 0); + + if (isDroppedFlag) { + item->sv.ent_flags |= SVFL_OBJECTIVE_DROPPED; + item->sv.objective_state = objective_state_t::Dropped; + + if (item->nextthink.milliseconds() > 0) { + const gtime_t pendingRespawnTime = (item->nextthink - level.time); + item->sv.respawntime = static_cast(pendingRespawnTime.milliseconds()); + } + } else if (isHiddenFlag && isRespawning) { + item->sv.ent_flags |= SVFL_OBJECTIVE_CARRIED; + item->sv.objective_state = objective_state_t::Carried; + + if (item->sv.respawntime == 0) { + item->sv.respawntime = Item_UnknownRespawnTime; + } + } else { + item->sv.ent_flags |= SVFL_OBJECTIVE_AT_BASE; + item->sv.objective_state = objective_state_t::AtBase; + } } // always need to update these for items, since random item spawning @@ -275,6 +308,7 @@ Trap_UpdateState static void Trap_UpdateState(gentity_t *danger) { danger->sv.ent_flags = SVFL_TRAP_DANGER; danger->sv.velocity = danger->velocity; + danger->sv.objective_state = objective_state_t::None; if (danger->owner != nullptr && danger->owner->client != nullptr) { player_skinnum_t pl_skinnum; @@ -314,6 +348,7 @@ Mover_UpdateState static void Mover_UpdateState(gentity_t *entity) { entity->sv.ent_flags = SVFL_NONE; entity->sv.health = entity->health; + entity->sv.objective_state = objective_state_t::None; if (entity->takedamage) { entity->sv.ent_flags |= SVFL_TAKES_DAMAGE; diff --git a/src/game.h b/src/game.h index f0f32e2..dc40e61 100644 --- a/src/game.h +++ b/src/game.h @@ -1677,11 +1677,21 @@ enum g_ent_flags_t : uint64_t { SVFL_WAS_TELEFRAGGED = bit_v< 26 >, SVFL_TRAP_DANGER = bit_v< 27 >, SVFL_ACTIVE = bit_v< 28 >, - SVFL_IS_SPECTATOR = bit_v< 29 >, - SVFL_IN_TEAM = bit_v< 30 > +SVFL_IS_SPECTATOR = bit_v< 29 >, +SVFL_IN_TEAM = bit_v< 30 >, +SVFL_OBJECTIVE_AT_BASE = bit_v< 31 >, +SVFL_OBJECTIVE_CARRIED = bit_v< 32 >, +SVFL_OBJECTIVE_DROPPED = bit_v< 33 > }; MAKE_ENUM_BITFLAGS(g_ent_flags_t); +enum class objective_state_t : int32_t { +None = 0, +AtBase, +Carried, +Dropped +}; + static constexpr int Max_Armor_Types = 3; struct armorInfo_t { @@ -1691,9 +1701,10 @@ struct armorInfo_t { // Used by AI/Tools on the engine side... struct g_entity_t { - bool init; - g_ent_flags_t ent_flags; - button_t buttons; +bool init; +g_ent_flags_t ent_flags; +objective_state_t objective_state = objective_state_t::None; +button_t buttons; uint32_t spawnflags; int32_t item_id; int32_t armor_type; diff --git a/tests/ctf_flag_state_tests.cpp b/tests/ctf_flag_state_tests.cpp new file mode 100644 index 0000000..170158e --- /dev/null +++ b/tests/ctf_flag_state_tests.cpp @@ -0,0 +1,263 @@ +#include "../src/g_local.h" +#include "../src/bots/bot_utils.h" +#include +#include +#include + +local_game_import_t gi{}; +level_locals_t level{}; +game_export_t globals{}; + +static constexpr size_t kTestEntityCount = 8; +static std::aligned_storage_t g_entity_storage[kTestEntityCount]; +gentity_t *g_entities = reinterpret_cast(g_entity_storage); +static size_t g_next_entity = 0; + +/* +============= +AllocTestEntity + +Provides zeroed entity storage for simulation tests. +============= +*/ +static gentity_t *AllocTestEntity() +{ + assert(g_next_entity < kTestEntityCount); + gentity_t *ent = reinterpret_cast(&g_entity_storage[g_next_entity++]); + std::memset(ent, 0, sizeof(gentity_t)); + return ent; +} + +/* +============= +StubInfoValueForKey + +Provides a predictable value for name lookups during testing. +============= +*/ +static size_t StubInfoValueForKey(const char *, const char *, char *buffer, size_t buffer_len) +{ + if (buffer_len > 0) + buffer[0] = '\0'; + + return 0; +} + +/* +============= +StubBotRegisterEntity + +Tracks entity registration attempts without engine side effects. +============= +*/ +static void StubBotRegisterEntity(const gentity_t *) +{ +} + +/* +============= +StubTrace + +Returns an empty trace result for planner lookups. +============= +*/ +static trace_t StubTrace(gvec3_cref_t, gvec3_cptr_t, gvec3_cptr_t, gvec3_cref_t, const gentity_t *, contents_t) +{ + trace_t tr{}; + return tr; +} + +/* +============= +StubComPrint + +Ignores formatted bot utility logging during tests. +============= +*/ +static void StubComPrint(const char *) +{ +} + +/* +============= +StubAngleVectors + +Zeroes output vectors for tests that depend on angle calculations. +============= +*/ +void AngleVectors(const vec3_t &, vec3_t &forward, vec3_t *right, vec3_t *up) +{ + forward = { 0.0f, 0.0f, 0.0f }; + if (right) + *right = { 0.0f, 0.0f, 0.0f }; + if (up) + *up = { 0.0f, 0.0f, 0.0f }; +} + +/* +============= +ClientIsPlaying +============= +*/ +bool ClientIsPlaying(gclient_t *) +{ + return true; +} + +/* +============= +ArmorIndex +============= +*/ +item_id_t ArmorIndex(gentity_t *) +{ + return IT_ARMOR_BODY; +} + +/* +============= +P_GetLobbyUserNum +============= +*/ +unsigned int P_GetLobbyUserNum(const gentity_t *) +{ + return 0; +} + +/* +============= +G_FreeEntity +============= +*/ +void G_FreeEntity(gentity_t *) +{ +} + +/* +============= +MakeFlagItem + +Creates a minimal flag item descriptor for tests. +============= +*/ +static gitem_t MakeFlagItem(item_id_t id, const char *classname) +{ + gitem_t item{}; + item.id = id; + item.classname = classname; + return item; +} + +/* +============= +MakeFlagEntity + +Constructs a minimal flag entity for simulation tests. +============= +*/ +static gentity_t *MakeFlagEntity(gitem_t *item, const vec3_t &origin) +{ + gentity_t *flag = AllocTestEntity(); + flag->item = item; + flag->classname = item->classname; + flag->s.origin = origin; + flag->solid = SOLID_TRIGGER; + flag->svflags = SVF_NONE; + return flag; +} + +/* +============= +TestFlagAtBaseState + +Verifies that a visible flag at its spawn point is reported as home. +============= +*/ +static void TestFlagAtBaseState() +{ + level.time = 0_ms; + + gitem_t red_flag_item = MakeFlagItem(IT_FLAG_RED, ITEM_CTF_FLAG_RED); + gentity_t *flag = MakeFlagEntity(&red_flag_item, { 16.0f, -8.0f, 4.0f }); + + Entity_UpdateState(flag); + + assert(flag->sv.team == TEAM_RED); + assert(flag->sv.ent_flags & SVFL_IS_OBJECTIVE); + assert(flag->sv.ent_flags & SVFL_OBJECTIVE_AT_BASE); + assert(flag->sv.objective_state == objective_state_t::AtBase); + assert(flag->sv.start_origin == flag->sv.end_origin); +} + +/* +============= +TestFlagCarriedState + +Ensures a hidden respawning flag is marked as carried. +============= +*/ +static void TestFlagCarriedState() +{ + level.time = 5_sec; + + gitem_t blue_flag_item = MakeFlagItem(IT_FLAG_BLUE, ITEM_CTF_FLAG_BLUE); + gentity_t *flag = MakeFlagEntity(&blue_flag_item, { -24.0f, 12.0f, 0.0f }); + flag->solid = SOLID_NOT; + flag->svflags = SVF_NOCLIENT; + flag->flags = FL_RESPAWN; + + Entity_UpdateState(flag); + + assert(flag->sv.team == TEAM_BLUE); + assert(flag->sv.ent_flags & SVFL_IS_HIDDEN); + assert(flag->sv.ent_flags & SVFL_OBJECTIVE_CARRIED); + assert(flag->sv.objective_state == objective_state_t::Carried); + assert(flag->sv.respawntime == Item_UnknownRespawnTime); +} + +/* +============= +TestFlagDroppedState + +Confirms a dropped flag reports its dropped state and return timer. +============= +*/ +static void TestFlagDroppedState() +{ + level.time = 12_sec; + + gitem_t red_flag_item = MakeFlagItem(IT_FLAG_RED, ITEM_CTF_FLAG_RED); + gentity_t *carrier = AllocTestEntity(); + gentity_t *flag = MakeFlagEntity(&red_flag_item, { 4.0f, 4.0f, 32.0f }); + flag->spawnflags = SPAWNFLAG_ITEM_DROPPED; + flag->owner = carrier; + flag->nextthink = level.time + 30_sec; + + Entity_UpdateState(flag); + + assert(flag->sv.ent_flags & SVFL_OBJECTIVE_DROPPED); + assert(flag->sv.objective_state == objective_state_t::Dropped); + assert(flag->sv.respawntime == 30000); +} + +/* +============= +main +============= +*/ +int main() +{ + g_next_entity = 0; + std::memset(g_entity_storage, 0, sizeof(g_entity_storage)); + gi.Info_ValueForKey = StubInfoValueForKey; + gi.Bot_RegisterEntity = StubBotRegisterEntity; + gi.game_import_t::trace = StubTrace; + gi.Com_Print = StubComPrint; + globals.num_entities = kTestEntityCount; + + TestFlagAtBaseState(); + TestFlagCarriedState(); + TestFlagDroppedState(); + + return 0; +} From 233dbb481ce03060c60539fbeb20b9a87b735dc9 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 21:10:16 +0000 Subject: [PATCH 125/142] Refactor G_Find to use span --- src/g_utils.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 4b64dbc..e3e4586 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -7,6 +7,7 @@ #include "g_utils_friendly_message.h" #include #include +#include #include "g_utils_target_selection.h" /* @@ -20,16 +21,20 @@ nullptr will be returned if the end of the list is reached. ============= */ gentity_t* G_Find(gentity_t* from, std::function matcher) { - if (!from) - from = g_entities; - else - from++; + const std::span entities{ g_entities, static_cast(globals.num_entities) }; + size_t start_index = 0; - for (; from < &g_entities[globals.num_entities]; from++) { - if (!from->inuse) + if (from) + start_index = static_cast((from - g_entities) + 1); + + if (start_index >= entities.size()) + return nullptr; + + for (gentity_t& ent : entities.subspan(start_index)) { + if (!ent.inuse) continue; - if (matcher(from)) - return from; + if (matcher(&ent)) + return &ent; } return nullptr; From 27588fe87669e86bc2452830ce865694b53005e0 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Sun, 23 Nov 2025 21:10:42 +0000 Subject: [PATCH 126/142] Refactor findradius center distance calculation --- src/g_utils.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 4b64dbc..85c7716 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -45,10 +45,6 @@ findradius (origin, radius) ================= */ gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { - const auto compute_center = [](const gentity_t* ent) { - return ent->s.origin + (ent->mins + ent->maxs) * 0.5f; - }; - if (!from) from = g_entities; else @@ -58,7 +54,8 @@ gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { continue; if (from->solid == SOLID_NOT) continue; - const vec3_t eorg = org - compute_center(from); + const vec3_t entity_center = from->s.origin + (from->mins + from->maxs) * 0.5f; + const vec3_t eorg = org - entity_center; if (eorg.length() > rad) continue; return from; @@ -69,6 +66,7 @@ gentity_t* findradius(gentity_t* from, const vec3_t& org, float rad) { + /* ============= G_PickTarget From 9dfb633a69256314f7257e1601d5d4b8966a3217 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:47:04 +0000 Subject: [PATCH 127/142] Handle MOTD buffer lifecycle --- src/g_local.h | 1 + src/g_main.cpp | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/g_local.h b/src/g_local.h index 0e54fa3..32b52ec 100644 --- a/src/g_local.h +++ b/src/g_local.h @@ -1455,6 +1455,7 @@ struct game_locals_t { gametype_t gametype; std::string motd; + char *motd_buffer = nullptr; int motd_mod_count = 0; ruleset_t ruleset; diff --git a/src/g_main.cpp b/src/g_main.cpp index 5230244..ae6c2b0 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -606,6 +606,13 @@ Loads the server message of the day text into persistent memory. ============= */ void G_LoadMOTD() { + if (game.motd_buffer) { + gi.TagFree(game.motd_buffer); + game.motd_buffer = nullptr; + } + + game.motd.clear(); + // load up ent override const char *name = G_Fmt("baseq2/{}", g_motd_filename->string[0] ? g_motd_filename->string : "motd.txt").data(); FILE *f = fopen(name, "rb"); @@ -638,14 +645,19 @@ void G_LoadMOTD() { fclose(f); if (valid) { - game.motd = (const char *)buffer; + game.motd_buffer = buffer; + game.motd.assign(buffer, length); game.motd_mod_count++; if (g_verbose->integer) gi.Com_PrintFmt("{}: MotD file verified and loaded: \"{}\"\n", __FUNCTION__, name); } else { gi.Com_PrintFmt("{}: MotD file load error for \"{}\", discarding.\n", __FUNCTION__, name); - if (buffer) + if (buffer) { gi.TagFree(buffer); + buffer = nullptr; + } + game.motd_buffer = nullptr; + game.motd.clear(); } } } From 8e87c77a215352250cac21deb23c6ecf7e32e8ab Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:48:00 +0000 Subject: [PATCH 128/142] Handle empty Horde player lists --- src/g_main.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index 5230244..143e2ab 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -574,18 +574,14 @@ bool Horde_LivesEnabled() { ============= Horde_NoLivesRemain -Returns true when every playing client is out of lives or eliminated. +Returns true when no active players are present or when every playing client is out of lives or eliminated. ============= */ bool Horde_NoLivesRemain(const std::vector &states) { - bool have_players = false; - for (const auto &state : states) { if (!state.playing) continue; - have_players = true; - if (state.health > 0) return false; @@ -593,7 +589,7 @@ bool Horde_NoLivesRemain(const std::vector &states) { return false; } - return have_players; + return true; } // ================================================= From b450ce0d3ed0a163eca5ef459e026959ab5ac275 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:51:33 +0000 Subject: [PATCH 129/142] Add null check for menu status bar entries --- src/p_menu_statusbar.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/p_menu_statusbar.cpp b/src/p_menu_statusbar.cpp index f605a94..0de9d25 100644 --- a/src/p_menu_statusbar.cpp +++ b/src/p_menu_statusbar.cpp @@ -115,6 +115,9 @@ size_t P_Menu_BuildStatusBar(const menu_hnd_t *hnd, char *layout, size_t layout_ layout[0] = '\0'; + if (!hnd->entries) + return 0; + P_Menu_Appendf(layout, layout_size, "xv %d yv %d picn %s ", 32, 8, "inventory"); bool alt = false; From 4e17911aea054baf06e28235c905bb75107b3e13 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:52:23 +0000 Subject: [PATCH 130/142] Adjust status bar caret alignment --- src/p_menu_statusbar.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/p_menu_statusbar.cpp b/src/p_menu_statusbar.cpp index f605a94..60a09c7 100644 --- a/src/p_menu_statusbar.cpp +++ b/src/p_menu_statusbar.cpp @@ -35,11 +35,10 @@ size_t Q_strlcpy(char *dst, const char *src, size_t siz) *d = '\0'; while (*s++) ; - } + } return static_cast(s - src - 1); -} - + } /* ============= Q_strlcat @@ -67,19 +66,17 @@ size_t Q_strlcat(char *dst, const char *src, size_t siz) if (n != 1) { *d++ = *s; n--; - } - s++; - } + } s++; +} *d = '\0'; return dlen + static_cast(s - src); -} -#endif + }#endif /* ============= -P_Menu_Appendf + P_Menu_Appendf Appends formatted text to a layout buffer while ensuring the destination is not overrun. @@ -98,8 +95,7 @@ static bool P_Menu_Appendf(char *layout, size_t layout_size, const char *fmt, .. va_end(args); return Q_strlcat(layout, chunk.data(), layout_size) < layout_size; -} - + } /* ============= P_Menu_BuildStatusBar @@ -131,24 +127,25 @@ size_t P_Menu_BuildStatusBar(const menu_hnd_t *hnd, char *layout, size_t layout_ alt = true; t++; } - const int y = 32 + i * 8; int x = 64; + int caret_x = 56; const char *loc_func = "loc_string"; if (p->align == MENU_ALIGN_CENTER) { x = 0; + caret_x = 152; loc_func = "loc_cstring"; } else if (p->align == MENU_ALIGN_RIGHT) { x = 260; + caret_x = 252; loc_func = "loc_rstring"; } - P_Menu_Appendf(layout, layout_size, "yv %d ", y); P_Menu_Appendf(layout, layout_size, "xv %d %s%s 1 \"%s\" \"%s\" ", x, loc_func, (hnd->cur == i || alt) ? "2" : "", t, p->text_arg1); if (hnd->cur == i) - P_Menu_Appendf(layout, layout_size, "xv %d string2 \"%s\" ", 56, ">\""); + P_Menu_Appendf(layout, layout_size, "xv %d string2 \"%s\" ", caret_x, ">\""); alt = false; } From ce72d67e4977a3b947dea34c034c96cbfbe5bf2b Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:52:43 +0000 Subject: [PATCH 131/142] Restore writeip persistence --- src/g_svcmds.cpp | 61 +++++---- tests/ip_filter_write_tests.cpp | 221 ++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 25 deletions(-) create mode 100644 tests/ip_filter_write_tests.cpp diff --git a/src/g_svcmds.cpp b/src/g_svcmds.cpp index faddc5b..9275f06 100644 --- a/src/g_svcmds.cpp +++ b/src/g_svcmds.cpp @@ -3,6 +3,10 @@ #include "g_local.h" #include +#include +#include +#include +#include /* ============= @@ -225,45 +229,52 @@ static void SVCmd_NextMap_f() { /* ================= -G_WriteIP_f +SVCmd_WriteIP_f + +Serializes the current filterban value and IP filters to listip.cfg using +platform-aware file handling. ================= */ static void SVCmd_WriteIP_f(void) { - // KEX_FIXME: Sys_FOpen isn't available atm, just commenting this out since i don't think we even need this functionality - sponge - /* - FILE* f; + byte b[4]; + int i; + cvar_t *game; - byte b[4]; - int i; - cvar_t* game; + game = gi.cvar("game", "", static_cast(0)); - game = gi.cvar("game", "", 0); + std::filesystem::path listip_path = *game->string ? std::filesystem::path(game->string) : std::filesystem::path(GAMEVERSION); + listip_path /= "listip.cfg"; - std::string name; - if (!*game->string) - name = std::string(GAMEVERSION) + "/listip.cfg"; - else - name = std::string(game->string) + "/listip.cfg"; + gi.LocClient_Print(nullptr, PRINT_HIGH, "Writing {}.\n", listip_path.string().c_str()); - gi.LocClient_Print(nullptr, PRINT_HIGH, "Writing {}.\n", name.c_str()); + if (!listip_path.parent_path().empty()) { + std::error_code create_error; + std::filesystem::create_directories(listip_path.parent_path(), create_error); + if (create_error) { + gi.LocClient_Print(nullptr, PRINT_HIGH, "Couldn't write {} ({})\n", listip_path.string().c_str(), create_error.message().c_str()); + return; + } + } - f = Sys_FOpen(name.c_str(), "wb"); - if (!f) - { - gi.LocClient_Print(nullptr, PRINT_HIGH, "Couldn't open {}\n", name.c_str()); + std::ofstream file(listip_path, std::ios::out | std::ios::binary | std::ios::trunc); + if (!file.is_open()) { + const std::error_code open_error(errno, std::generic_category()); + gi.LocClient_Print(nullptr, PRINT_HIGH, "Couldn't write {} ({})\n", listip_path.string().c_str(), open_error.message().c_str()); return; } - fprintf(f, "set filterban %d\n", filterban->integer); + file << "set filterban " << filterban->integer << '\n'; - for (i = 0; i < numipfilters; i++) - { - *(unsigned*)b = ipfilters[i].compare; - fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]); + for (i = 0; i < numipfilters; i++) { + *(unsigned *)b = ipfilters[i].compare; + file << "sv addip " << static_cast(b[0]) << '.' << static_cast(b[1]) << '.' << static_cast(b[2]) << '.' << static_cast(b[3]) << '\n'; } - fclose(f); - */ + file.close(); + if (file.fail()) { + const std::error_code close_error(errno, std::generic_category()); + gi.LocClient_Print(nullptr, PRINT_HIGH, "Couldn't write {} ({})\n", listip_path.string().c_str(), close_error.message().c_str()); + } } /* diff --git a/tests/ip_filter_write_tests.cpp b/tests/ip_filter_write_tests.cpp new file mode 100644 index 0000000..c52cd19 --- /dev/null +++ b/tests/ip_filter_write_tests.cpp @@ -0,0 +1,221 @@ +#include "../src/g_local.h" +#include +#include +#include +#include +#include +#include +#include + +local_game_import_t gi{}; +g_fmt_data_t g_fmt_data{}; + +static int g_failures = 0; +static std::vector g_messages; + +std::array local_game_import_t::buffers; +std::array local_game_import_t::buffer_ptrs; + +static char game_name[] = "game"; +static std::vector game_string_storage; +static cvar_t game_cvar{ + game_name, + nullptr, + nullptr, + static_cast(0), + 0, + 0.0f, + nullptr, + 0 +}; + +static char filterban_name[] = "filterban"; +static char filterban_value[] = "1"; +static cvar_t filterban_stub{ + filterban_name, + filterban_value, + nullptr, + static_cast(0), + 0, + 0.0f, + nullptr, + 1 +}; + +cvar_t *filterban = &filterban_stub; + +/* +============= +StubArgc + +Provides a default argc of zero for command stubs. +============= +*/ +static int StubArgc() +{ + return 0; +} + +/* +============= +StubArgv + +Provides empty argv content for command stubs. +============= +*/ +static const char *StubArgv(int) +{ + return ""; +} + +/* +============= +StubCvar + +Returns the configured game cvar or nullptr for unknown names. +============= +*/ +static cvar_t *StubCvar(const char *var_name, const char *, cvar_flags_t) +{ + if (std::strcmp(var_name, game_name) == 0) + { + return &game_cvar; + } + + return nullptr; +} + +/* +============= +StubLocPrint + +Captures localization print requests for verification. +============= +*/ +static void StubLocPrint(gentity_t *, print_type_t, const char *base, const char **args, size_t num_args) +{ + std::string message = base; + for (size_t i = 0; i < num_args; ++i) + { + message += args[i]; + } + + g_messages.push_back(message); +} + +/* +============= +Match_End + +Stubbed to satisfy linkage when including g_svcmds.cpp. +============= +*/ +void Match_End() +{ +} + +#include "../src/g_svcmds.cpp" + +/* +============= +Q_strcasecmp + +Implements case-insensitive string comparison for test coverage. +============= +*/ +int Q_strcasecmp(const char *s1, const char *s2) +{ + return strcasecmp(s1, s2); +} + +/* +============= +Expect + +Reports a failed expectation and increments the failure counter. +============= +*/ +static void Expect(bool condition, const char *message) +{ + if (!condition) + { + std::fprintf(stderr, "Expectation failed: %s\n", message); + ++g_failures; + } +} + +/* +============= +ConfigureGamePath + +Assigns the game cvar's string storage to the provided path. +============= +*/ +static void ConfigureGamePath(const std::filesystem::path &path) +{ + const auto string_data = path.string(); + game_string_storage.assign(string_data.begin(), string_data.end()); + game_string_storage.push_back('\0'); + game_cvar.string = game_string_storage.data(); +} + +/* +============= +ReadFileContents + +Returns the full text of the specified file. +============= +*/ +static std::string ReadFileContents(const std::filesystem::path &path) +{ + std::ifstream stream(path); + return std::string(std::istreambuf_iterator(stream), std::istreambuf_iterator()); +} + +/* +============= +ValidateWriteIpSerialization + +Ensures writeip persists filterban and IP filters to listip.cfg. +============= +*/ +static void ValidateWriteIpSerialization() +{ + gi.argc = &StubArgc; + gi.argv = &StubArgv; + gi.cvar = &StubCvar; + gi.Loc_Print = &StubLocPrint; + + numipfilters = 0; + Expect(StringToFilter("10.0.0.1", &ipfilters[numipfilters++]), "Should parse 10.0.0.1"); + Expect(StringToFilter("192.168.5.0", &ipfilters[numipfilters++]), "Should parse 192.168.5.0"); + + std::filesystem::path output_dir = std::filesystem::temp_directory_path() / "muffmode_writeip"; + std::filesystem::remove_all(output_dir); + std::filesystem::create_directories(output_dir); + ConfigureGamePath(output_dir); + + SVCmd_WriteIP_f(); + + std::filesystem::path output_file = output_dir / "listip.cfg"; + Expect(std::filesystem::exists(output_file), "listip.cfg should be created"); + + const std::string file_contents = ReadFileContents(output_file); + const std::string expected_contents = "set filterban 1\nsv addip 10.0.0.1\nsv addip 192.168.5.0\n"; + Expect(file_contents == expected_contents, "listip.cfg should include filterban and both entries"); + + std::filesystem::remove_all(output_dir); +} + +/* +============= +main + +Executes writeip serialization coverage. +============= +*/ +int main() +{ + ValidateWriteIpSerialization(); + return g_failures == 0 ? 0 : 1; +} From ccab40bcfc874e1154404db03a383c7c9e1883dc Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:53:10 +0000 Subject: [PATCH 132/142] Add RAII guard for JSON stack handling --- src/g_save.cpp | 71 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index adaddd4..fcf53b3 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -219,6 +219,40 @@ const save_data_list_t *save_data_list_t::fetch(const void *ptr, save_data_tag_t std::string json_error_stack; +/* +============= +json_stack_segment + +Pushes a stack segment on construction and pops it on destruction to ensure +balanced JSON stack usage. +============= +*/ +class json_stack_segment { +public: + /* + ============= + json_stack_segment + + Pushes the provided stack segment when the guard is constructed. + ============= + */ + template + explicit json_stack_segment(const T &stack) { + json_push_stack(stack); + } + + /* + ============= + ~json_stack_segment + + Pops the active stack segment when the guard is destroyed. + ============= + */ + ~json_stack_segment() { + json_pop_stack(); + } +}; + /* ============= json_push_stack @@ -1778,9 +1812,8 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t return; case ST_STRUCT: if (!json.isNull()) { - json_push_stack(field); + json_stack_segment stack_guard(field); read_save_struct_json(json, data, type->structure); - json_pop_stack(); } return; case ST_ENTITY: @@ -1855,18 +1888,16 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t const Json::Value &value = *it; if (!value.isInt()) { - json_push_stack(classname); + json_stack_segment stack_guard(classname); json_print_error(field, "expected integer", false); - json_pop_stack(); continue; } gitem_t *item = FindItemByClassname(classname); if (!item) { - json_push_stack(classname); + json_stack_segment stack_guard(classname); json_print_error(field, G_Fmt("can't find item {}", classname).data(), false); - json_pop_stack(); continue; } @@ -1890,45 +1921,39 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t const Json::Value &value = json[i]; if (!value.isObject()) { - json_push_stack(fmt::format("{}", i)); + json_stack_segment stack_guard(fmt::format("{}", i)); json_print_error(field, "expected object", false); - json_pop_stack(); continue; } // quick type checks if (!value["classname"].isString()) { - json_push_stack(fmt::format("{}.classname", i)); + json_stack_segment stack_guard(fmt::format("{}.classname", i)); json_print_error(field, "expected string", false); - json_pop_stack(); continue; } if (!value["mins"].isArray() || value["mins"].size() != 3) { - json_push_stack(fmt::format("{}.mins", i)); + json_stack_segment stack_guard(fmt::format("{}.mins", i)); json_print_error(field, "expected array[3]", false); - json_pop_stack(); continue; } if (!value["maxs"].isArray() || value["maxs"].size() != 3) { - json_push_stack(fmt::format("{}.maxs", i)); + json_stack_segment stack_guard(fmt::format("{}.maxs", i)); json_print_error(field, "expected array[3]", false); - json_pop_stack(); continue; } if (!value["strength"].isInt()) { - json_push_stack(fmt::format("{}.strength", i)); + json_stack_segment stack_guard(fmt::format("{}.strength", i)); json_print_error(field, "expected int", false); - json_pop_stack(); continue; } p->classname = G_CopyString(value["classname"].asCString(), TAG_LEVEL); p->strength = value["strength"].asInt(); - for (int32_t x = 0; x < 3; x++) { p->mins[x] = value["mins"][x].asInt(); p->maxs[x] = value["maxs"][x].asInt(); @@ -2513,9 +2538,8 @@ void ReadGameJson(const char *jsonString) { globals.gentities = g_entities; // read game - json_push_stack("game"); + json_stack_segment game_stack("game"); read_save_struct_json(json["game"], &game, &game_locals_t_savestruct); - json_pop_stack(); // read clients const Json::Value &clients = json["clients"]; @@ -2528,9 +2552,8 @@ void ReadGameJson(const char *jsonString) { size_t i = 0; for (auto &v : clients) { - json_push_stack(fmt::format("clients[{}]", i)); + json_stack_segment stack_guard(fmt::format("clients[{}]", i)); read_save_struct_json(v, &game.clients[i++], &gclient_t_savestruct); - json_pop_stack(); } PrecacheInventoryItems(); @@ -2602,9 +2625,8 @@ void ReadLevelJson(const char *jsonString) { globals.num_entities = game.maxclients + 1; // read level - json_push_stack("level"); + json_stack_segment level_stack("level"); read_save_struct_json(json["level"], &level, &level_locals_t_savestruct); - json_pop_stack(); // read entities const Json::Value &entities = json["entities"]; @@ -2624,9 +2646,8 @@ void ReadLevelJson(const char *jsonString) { gentity_t *ent = &g_entities[number]; G_InitGentity(ent); - json_push_stack(fmt::format("entities[{}]", number)); + json_stack_segment stack_guard(fmt::format("entities[{}]", number)); read_save_struct_json(value, ent, &gentity_t_savestruct); - json_pop_stack(); gi.linkentity(ent); } From c7010582ea7e45e3f4d8732b51cfe280238a3f90 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:53:50 +0000 Subject: [PATCH 133/142] Stop emitting warnings for fatal JSON errors --- src/g_save.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/g_save.cpp b/src/g_save.cpp index adaddd4..90f9dc5 100644 --- a/src/g_save.cpp +++ b/src/g_save.cpp @@ -252,9 +252,12 @@ Prints JSON load errors with the current stack context, optionally treating them ============= */ void json_print_error(const char *field, const char *message, bool fatal) { - if (fatal || g_strict_saves->integer) + if (fatal || g_strict_saves->integer) { gi.Com_ErrorFmt("Error loading JSON\n{}.{}: {}", json_error_stack, field, message); + return; + } + gi.Com_PrintFmt("Warning loading JSON\n{}.{}: {}\n", json_error_stack, field, message); } From 1a8665d51ff6ce22f449d17bfb1ac18260c8fc21 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:54:28 +0000 Subject: [PATCH 134/142] Fix vampiric regen cap rounding and add regression test --- src/g_items.cpp | 2 +- src/g_items_limits.h | 17 +++++++++++++++++ tests/autodoc_regen_tests.cpp | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/autodoc_regen_tests.cpp diff --git a/src/g_items.cpp b/src/g_items.cpp index 2385f9a..087b1dd 100644 --- a/src/g_items.cpp +++ b/src/g_items.cpp @@ -1140,7 +1140,7 @@ void Tech_ApplyAutoDoc(gentity_t* ent) { bool mod = g_instagib->integer || g_nadefest->integer; bool has_autodoc = false; bool no_health = mod || GTF(GTF_ARENA) || g_no_health->integer; - int max = g_vampiric_damage->integer ? ceil(g_vampiric_health_max->integer / 2) : mod ? 100 : 150; + int max = G_GetTechRegenMax(g_vampiric_health_max->integer, g_vampiric_damage->integer, mod); cl = ent->client; if (!cl) diff --git a/src/g_items_limits.h b/src/g_items_limits.h index d61ec35..7073b64 100644 --- a/src/g_items_limits.h +++ b/src/g_items_limits.h @@ -1,5 +1,7 @@ #pragma once +#include + /* ============= G_GetHoldableMax @@ -13,6 +15,21 @@ constexpr int G_GetHoldableMax(int override_max, int item_max, int fallback) return max_value > 0 ? max_value : fallback; } +/* +============= +G_GetTechRegenMax + +Returns the regeneration cap applied when the Autodoc tech is active. +============= +*/ +inline int G_GetTechRegenMax(int vampiric_health_max, bool vampiric_damage_enabled, bool mod_enabled) +{ + if (vampiric_damage_enabled) + return (int)ceil(static_cast(vampiric_health_max) / 2.0f); + + return mod_enabled ? 100 : 150; +} + static_assert(G_GetHoldableMax(3, 1, 1) == 3, "Override max should win when positive."); static_assert(G_GetHoldableMax(0, 2, 1) == 2, "Item max should win when override is unset."); static_assert(G_GetHoldableMax(0, 0, 1) == 1, "Fallback should apply when no max is defined."); diff --git a/tests/autodoc_regen_tests.cpp b/tests/autodoc_regen_tests.cpp new file mode 100644 index 0000000..209cfac --- /dev/null +++ b/tests/autodoc_regen_tests.cpp @@ -0,0 +1,17 @@ +#include "../src/g_items_limits.h" +#include + +/* +============= +main + +Verifies vampiric regen cap rounds up for odd maximums. +============= +*/ +int main() +{ + assert(G_GetTechRegenMax(101, true, false) == 51); + assert(G_GetTechRegenMax(100, true, false) == 50); + + return 0; +} From bf3d1b6b7d7d5cebe583bd6f54b8cf46583f574c Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:54:46 +0000 Subject: [PATCH 135/142] Handle empty horde rosters in life check --- src/g_main.cpp | 2 +- tests/horde_lives_tests.cpp | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/g_main.cpp b/src/g_main.cpp index 5230244..a925171 100644 --- a/src/g_main.cpp +++ b/src/g_main.cpp @@ -593,7 +593,7 @@ bool Horde_NoLivesRemain(const std::vector &states) { return false; } - return have_players; + return true; } // ================================================= diff --git a/tests/horde_lives_tests.cpp b/tests/horde_lives_tests.cpp index ccf69b9..203f2b8 100644 --- a/tests/horde_lives_tests.cpp +++ b/tests/horde_lives_tests.cpp @@ -71,6 +71,20 @@ static void TestNoLivesTriggersElimination() Expect(Horde_NoLivesRemain(states), "No lives should trigger elimination"); } +/* +============= +TestEmptyRosterTriggersElimination + +Ensures waves complete when no playing clients remain. +============= +*/ +static void TestEmptyRosterTriggersElimination() +{ + std::vector states; + + Expect(Horde_NoLivesRemain(states), "No playing clients should end the wave"); +} + /* ============= main @@ -83,6 +97,7 @@ int main() TestAlivePlayerPreventsElimination(); TestRemainingLivesPreventFailure(); TestNoLivesTriggersElimination(); + TestEmptyRosterTriggersElimination(); return g_failures == 0 ? 0 : 1; } From f08405e34b331ef5c069a1f5c4bfcc4ce19ebf64 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:55:08 +0000 Subject: [PATCH 136/142] Ensure spectators inherit team chat prefix --- src/g_utils.cpp | 8 +++++++- src/g_utils_friendly_message.h | 20 ++++++++++++++++++++ tests/friendly_message_tests.cpp | 4 ++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/g_utils.cpp b/src/g_utils.cpp index 9e84b2e..f807bd5 100644 --- a/src/g_utils.cpp +++ b/src/g_utils.cpp @@ -183,17 +183,23 @@ void BroadcastFriendlyMessage(team_t team, const char* msg) { for (auto ce : active_clients()) { const bool playing = ClientIsPlaying(ce->client); + bool following_team = false; if (!playing) { if (!Teams()) continue; gentity_t* follow = ce->client->follow_target; if (!follow || !follow->client || follow->client->sess.team != team) continue; + + following_team = true; } else if (Teams() && ce->client->sess.team != team) { continue; } - gi.LocClient_Print(ce, PRINT_HIGH, G_Fmt("{}{}", playing && ce->client->sess.team != TEAM_SPECTATOR ? "[TEAM]: " : "", msg).data()); + + const bool is_team_player = playing && ce->client->sess.team == team && ce->client->sess.team != TEAM_SPECTATOR; + const bool prefix_team = FriendlyMessageShouldPrefixTeam(Teams(), team == TEAM_SPECTATOR, playing, is_team_player, following_team); + gi.LocClient_Print(ce, PRINT_HIGH, G_Fmt("{}{}", prefix_team ? "[TEAM]: " : "", msg).data()); } } diff --git a/src/g_utils_friendly_message.h b/src/g_utils_friendly_message.h index ab70cbd..e0476e4 100644 --- a/src/g_utils_friendly_message.h +++ b/src/g_utils_friendly_message.h @@ -13,3 +13,23 @@ inline bool FriendlyMessageHasText(const char *msg) { return msg && *msg && strnlen(msg, 256) < 256; } + +/* +============= +FriendlyMessageShouldPrefixTeam + +Determines if a friendly message should include a team prefix for the +recipient based on game mode, target team, and how the recipient is viewing +the game. +============= +*/ +inline bool FriendlyMessageShouldPrefixTeam(bool teams_active, bool target_is_spectator, bool recipient_is_playing, bool recipient_on_team, bool recipient_following_team) +{ + if (!teams_active || target_is_spectator) + return false; + + if (recipient_is_playing) + return recipient_on_team; + + return recipient_following_team; +} diff --git a/tests/friendly_message_tests.cpp b/tests/friendly_message_tests.cpp index 71d32ce..d7e11b8 100644 --- a/tests/friendly_message_tests.cpp +++ b/tests/friendly_message_tests.cpp @@ -24,5 +24,9 @@ int main() bounded[255] = '\0'; assert(FriendlyMessageHasText(bounded)); + assert(FriendlyMessageShouldPrefixTeam(true, false, false, false, true)); + assert(!FriendlyMessageShouldPrefixTeam(true, false, false, false, false)); + assert(FriendlyMessageShouldPrefixTeam(true, false, true, true, false)); + return 0; } From b6f513b95d6651a6ceed6015cc5dbca73e756ce5 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 16:55:27 +0000 Subject: [PATCH 137/142] Reset shared state between CTF flag tests --- tests/ctf_flag_state_tests.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/tests/ctf_flag_state_tests.cpp b/tests/ctf_flag_state_tests.cpp index 170158e..e34fd75 100644 --- a/tests/ctf_flag_state_tests.cpp +++ b/tests/ctf_flag_state_tests.cpp @@ -242,18 +242,34 @@ static void TestFlagDroppedState() /* ============= -main +SetupTestEnvironment + +Resets shared test state and installs required engine stubs. ============= */ -int main() +static void SetupTestEnvironment() { g_next_entity = 0; std::memset(g_entity_storage, 0, sizeof(g_entity_storage)); + std::memset(&level, 0, sizeof(level)); + std::memset(&globals, 0, sizeof(globals)); + gi = {}; + gi.Info_ValueForKey = StubInfoValueForKey; gi.Bot_RegisterEntity = StubBotRegisterEntity; gi.game_import_t::trace = StubTrace; gi.Com_Print = StubComPrint; globals.num_entities = kTestEntityCount; +} + +/* +============= +main +============= +*/ +int main() +{ + SetupTestEnvironment(); TestFlagAtBaseState(); TestFlagCarriedState(); From b63737f2fa3e2cd04c6cc315c61481952d0ccb41 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 20:29:24 +0000 Subject: [PATCH 138/142] Sanitize player social id for config paths --- src/p_client.cpp | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index a36fd46..17c1dba 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -301,7 +301,40 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { if (!ent->client) return; if (ent->svflags & SVF_BOT) return; - const std::string path = std::string(G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id)); + const char *social_id = ent->client->pers.social_id; + if (!social_id || !*social_id) { + gi.Com_PrintFmt("{}: Invalid player social id.\n", __FUNCTION__); + return; + } + + if (std::strstr(social_id, "..")) { + gi.Com_PrintFmt("{}: Rejected player social id with traversal sequence.\n", __FUNCTION__); + return; + } + + std::string sanitized_id; + sanitized_id.reserve(std::strlen(social_id)); + + for (const char *c = social_id; *c; ++c) { + if (*c == '/' || *c == '\\') { + gi.Com_PrintFmt("{}: Rejected player social id containing path separators.\n", __FUNCTION__); + return; + } + + if (std::isalnum(static_cast(*c)) || *c == '_' || *c == '-') { + sanitized_id.push_back(*c); + } + else { + sanitized_id.push_back('_'); + } + } + + if (sanitized_id.empty()) { + gi.Com_PrintFmt("{}: Invalid player social id after sanitization.\n", __FUNCTION__); + return; + } + + const std::string path = std::string(G_Fmt("baseq2/pcfg/{}.cfg", sanitized_id)); const char *name = path.c_str(); FILE* f = fopen(name, "rb"); From c7c0be1161a8edaff15f6b9f5b78bf860c93e54e Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 20:30:37 +0000 Subject: [PATCH 139/142] Parse and apply player config settings --- src/p_client.cpp | 112 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 17 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index a36fd46..5f270f5 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -297,9 +297,13 @@ Load or create the player's configuration file on connect. static void PCfg_ClientInitPConfig(gentity_t* ent) { bool file_exists = false; bool cfg_valid = true; + client_config_t default_pc = ent->client->sess.pc; + client_config_t parsed_pc = default_pc; - if (!ent->client) return; - if (ent->svflags & SVF_BOT) return; + if (!ent->client) + return; + if (ent->svflags & SVF_BOT) + return; const std::string path = std::string(G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id)); const char *name = path.c_str(); @@ -339,27 +343,101 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { return; } - if (buffer) { - gi.TagFree(buffer); - buffer = nullptr; - } - } + if (buffer && length) { + char* cursor = buffer; + while (cursor && *cursor) { + char* line = cursor; + char* newline = std::strchr(cursor, '\n'); + if (newline) { + *newline = '\0'; + cursor = newline + 1; + } + else { + cursor = nullptr; + } + + while (*line == ' ' || *line == '\t') + ++line; + + if (!*line || (line[0] == '/' && line[1] == '/')) + continue; - if (!file_exists) { - f = fopen(name, "w"); - if (f) { - const std::string header = std::string(G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname)); + char* value = nullptr; + for (char* walker = line; *walker; ++walker) { + if (*walker == ' ' || *walker == '\t') { + *walker = '\0'; + value = walker + 1; + break; + } + } - fwrite(header.c_str(), 1, header.length(), f); - gi.Com_PrintFmt("{}: Player config written to: \"{}\"\n", __FUNCTION__, name); - fclose(f); + if (!value) + continue; + + while (*value == ' ' || *value == '\t') + ++value; + + char* comment = std::strstr(value, "//"); + if (comment) + *comment = '\0'; + + char* end_value = value + std::strlen(value); + while (end_value > value && (end_value[-1] == ' ' || end_value[-1] == '\t' || end_value[-1] == '\r')) + --end_value; + *end_value = '\0'; + + auto parse_bool = [](const char* str, bool default_value, bool* out) { + char* end_ptr = nullptr; + long val = std::strtol(str, &end_ptr, 10); + if (end_ptr == str || *end_ptr != '\0') + return default_value; + + if (val == 0) + return (*out = false); + if (val == 1) + return (*out = true); + + return default_value; + }; + + auto parse_int = [](const char* str, int default_value, int min, int max, int* out) { + char* end_ptr = nullptr; + long val = std::strtol(str, &end_ptr, 10); + if (end_ptr == str || *end_ptr != '\0') + return default_value; + + if (val < min || val > max) + return default_value; + + return (*out = static_cast(val)); + }; + + if (!std::strcmp(line, "show_id")) + parsed_pc.show_id = parse_bool(value, default_pc.show_id, &parsed_pc.show_id); + else if (!std::strcmp(line, "show_fragmessages")) + parsed_pc.show_fragmessages = parse_bool(value, default_pc.show_fragmessages, &parsed_pc.show_fragmessages); + else if (!std::strcmp(line, "show_timer")) + parsed_pc.show_timer = parse_bool(value, default_pc.show_timer, &parsed_pc.show_timer); + else if (!std::strcmp(line, "killbeep_num")) + parsed_pc.killbeep_num = parse_int(value, default_pc.killbeep_num, 0, 4, &parsed_pc.killbeep_num); + else if (!std::strcmp(line, "follow_killer")) + parsed_pc.follow_killer = parse_bool(value, default_pc.follow_killer, &parsed_pc.follow_killer); + else if (!std::strcmp(line, "follow_leader")) + parsed_pc.follow_leader = parse_bool(value, default_pc.follow_leader, &parsed_pc.follow_leader); + else if (!std::strcmp(line, "follow_powerup")) + parsed_pc.follow_powerup = parse_bool(value, default_pc.follow_powerup, &parsed_pc.follow_powerup); + else if (!std::strcmp(line, "use_expanded")) + parsed_pc.use_expanded = parse_bool(value, default_pc.use_expanded, &parsed_pc.use_expanded); + } + ent->client->sess.pc = parsed_pc; } - else { - gi.Com_PrintFmt("{}: Cannot save player config: {}\n", __FUNCTION__, name); + + if (buffer) { + gi.TagFree(buffer); + buffer = nullptr; } } } - //======================================================================= struct mon_name_t { const char* classname; From eae40745d23934324b6b6a97d1df2750ba721b47 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 20:31:56 +0000 Subject: [PATCH 140/142] Improve player config writer --- src/p_client.cpp | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index a36fd46..7a00532 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -262,31 +262,45 @@ static const char* ClientSkinOverride(const char* s) { // PLAYER CONFIGS //======================================================================= /* -static void PCfg_WriteConfig(gentity_t *ent) { +============= +PCfg_WriteConfig + +Persist the player's configuration to disk using the same validation as the loader. +============= +*/ +static void PCfg_WriteConfig(gentity_t* ent) { + if (!ent || !ent->client) + return; + if (ent->svflags & SVF_BOT) + return; if (!std::strcmp(ent->client->pers.social_id, "me_a_bot")) return; - const char *name = G_Fmt("baseq2/pcfg/wtest/{}.cfg", ent->client->pers.social_id).data(); - char *buffer = nullptr; + const std::string path = std::string(G_Fmt("baseq2/pcfg/{}.cfg", ent->client->pers.social_id)); + const char* name = path.c_str(); std::string string; - string.clear(); - - FILE *f = std::fopen(name, "wb"); - if (!f) + string = std::string(G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname)); + string += std::string(G_Fmt("name {}\n", ent->client->resp.netname)); + string += std::string(G_Fmt("show_id {}\n", ent->client->sess.pc.show_id ? 1 : 0)); + string += std::string(G_Fmt("show_fragmessages {}\n", ent->client->sess.pc.show_fragmessages ? 1 : 0)); + string += std::string(G_Fmt("show_timer {}\n", ent->client->sess.pc.show_timer ? 1 : 0)); + string += std::string(G_Fmt("killbeep_num {}\n", (int)ent->client->sess.pc.killbeep_num)); + + if (string.length() > 0x40000) { + gi.Com_PrintFmt("{}: Player config too large for \"{}\" ({} bytes).\n", __FUNCTION__, name, string.length()); return; + } - string = G_Fmt("// {}'s Player Config\n// Generated by Muff Mode\n", ent->client->resp.netname).data(); - string += G_Fmt("name {}\n", ent->client->resp.netname).data(); - string += G_Fmt("show_id {}\n", ent->client->sess.pc.show_id ? 1 : 0).data(); - string += G_Fmt("show_fragmessages {}\n", ent->client->sess.pc.show_fragmessages ? 1 : 0).data(); - string += G_Fmt("show_timer {}\n", ent->client->sess.pc.show_timer ? 1 : 0).data(); - string += G_Fmt("killbeep_num {}\n", (int)ent->client->sess.pc.killbeep_num).data(); + FILE* f = std::fopen(name, "wb"); + if (!f) { + gi.Com_PrintFmt("{}: Cannot save player config: {}\n", __FUNCTION__, name); + return; + } - std::fwrite(buffer, 1, strlen(buffer), f); + std::fwrite(string.c_str(), 1, string.length(), f); std::fclose(f); - gi.Com_PrintFmt("Player config written to: \"{}\"\n", name); + gi.Com_PrintFmt("{}: Player config written to: \"{}\"\n", __FUNCTION__, name); } -*/ /* ============= PCfg_ClientInitPConfig From 5e9fad6db51c3eb4012256dc72202ddf4550c392 Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 20:32:54 +0000 Subject: [PATCH 141/142] Handle pcfg file length errors --- src/p_client.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index a36fd46..c42e686 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -307,16 +307,28 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { FILE* f = fopen(name, "rb"); char* buffer = nullptr; if (f != NULL) { - size_t length; + size_t length = 0; size_t read_length; + long file_length = 0; - fseek(f, 0, SEEK_END); - length = ftell(f); - fseek(f, 0, SEEK_SET); - - if (length > 0x40000) { + if (fseek(f, 0, SEEK_END) != 0) { cfg_valid = false; } + if (cfg_valid) { + file_length = ftell(f); + if (file_length < 0) { + cfg_valid = false; + } + } + if (cfg_valid && fseek(f, 0, SEEK_SET) != 0) { + cfg_valid = false; + } + if (cfg_valid) { + length = static_cast(file_length); + if (length > 0x40000) { + cfg_valid = false; + } + } if (cfg_valid) { buffer = (char*)gi.TagMalloc(length + 1, TAG_GAME); if (length) { From 344702ca3e55ef6c4b45b3963728b30e4b2725ed Mon Sep 17 00:00:00 2001 From: themuffinator Date: Tue, 25 Nov 2025 20:37:56 +0000 Subject: [PATCH 142/142] Handle invalid player configs by regenerating defaults --- src/p_client.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/p_client.cpp b/src/p_client.cpp index 55decef..da51dbd 100644 --- a/src/p_client.cpp +++ b/src/p_client.cpp @@ -309,8 +309,9 @@ Load or create the player's configuration file on connect. ============= */ static void PCfg_ClientInitPConfig(gentity_t* ent) { - bool file_exists = false; - bool cfg_valid = true; +bool file_exists = false; +bool cfg_valid = true; +const char* cfg_error = nullptr; client_config_t default_pc = ent->client->sess.pc; client_config_t parsed_pc = default_pc; @@ -364,20 +365,24 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { if (fseek(f, 0, SEEK_END) != 0) { cfg_valid = false; + cfg_error = "seek to end failed"; } if (cfg_valid) { file_length = ftell(f); if (file_length < 0) { cfg_valid = false; + cfg_error = "file length negative"; } } if (cfg_valid && fseek(f, 0, SEEK_SET) != 0) { cfg_valid = false; + cfg_error = "seek to start failed"; } if (cfg_valid) { length = static_cast(file_length); if (length > 0x40000) { cfg_valid = false; + cfg_error = "config too large"; } } if (cfg_valid) { @@ -387,6 +392,7 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { if (length != read_length) { cfg_valid = false; + cfg_error = "partial config read"; } } buffer[length] = '\0'; @@ -398,9 +404,22 @@ static void PCfg_ClientInitPConfig(gentity_t* ent) { if (buffer) { gi.TagFree(buffer); } - gi.Com_PrintFmt("{}: Player config load error for \"{}\", discarding.\n", __FUNCTION__, name); + gi.Com_PrintFmt("{}: Player config load error for \"{}\" ({}) - regenerating default.\n", __FUNCTION__, name, cfg_error ? cfg_error : "unknown error"); + if (std::remove(name) != 0) { + FILE* truncate = std::fopen(name, "wb"); + if (truncate) { + std::fclose(truncate); + gi.Com_PrintFmt("{}: Truncated corrupt player config \"{}\".\n", __FUNCTION__, name); + } + else { + gi.Com_PrintFmt("{}: Failed to remove or truncate corrupt player config \"{}\".\n", __FUNCTION__, name); + } + } + ent->client->sess.pc = default_pc; + PCfg_WriteConfig(ent); return; } + } if (buffer && length) { char* cursor = buffer;