From: Mario Date: Fri, 1 Jul 2022 05:46:31 +0000 (+1000) Subject: Merge branch 'master' into Mario/survival X-Git-Url: http://git.xonotic.org/?a=commitdiff_plain;h=893f0da37d1e575eb5e9217ce498326e489deb75;hp=-c;p=xonotic%2Fxonotic-data.pk3dir.git Merge branch 'master' into Mario/survival --- 893f0da37d1e575eb5e9217ce498326e489deb75 diff --combined gamemodes-client.cfg index 45d0b7a3f,71d272a17..1bc0853c6 --- a/gamemodes-client.cfg +++ b/gamemodes-client.cfg @@@ -32,7 -32,6 +32,7 @@@ alias cl_hook_gamestart_k alias cl_hook_gamestart_ft alias cl_hook_gamestart_inv alias cl_hook_gamestart_duel +alias cl_hook_gamestart_sv - alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends + alias cl_hook_gameend alias cl_hook_shutdown alias cl_hook_activeweapon diff --combined gamemodes-server.cfg index da7108e80,318749021..297659090 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@@ -29,7 -29,6 +29,7 @@@ alias sv_hook_gamestart_k alias sv_hook_gamestart_ft alias sv_hook_gamestart_inv alias sv_hook_gamestart_duel +alias sv_hook_gamestart_sv // there is currently no hook for when the match is restarted // see sv_hook_readyrestart for previous uses of this hook //alias sv_hook_gamerestart @@@ -59,7 -58,6 +59,7 @@@ alias sv_vote_gametype_hook_on alias sv_vote_gametype_hook_rc alias sv_vote_gametype_hook_tdm alias sv_vote_gametype_hook_duel +alias sv_vote_gametype_hook_sv // Example preset to allow 1v1ctf to be used for the gametype voting screen. // Aliases can have max 31 chars so the gametype can have max 9 chars. @@@ -210,13 -208,6 +210,13 @@@ set g_duel_respawn_delay_large_count set g_duel_respawn_delay_max 0 set g_duel_respawn_waves 0 set g_duel_weapon_stay 0 +set g_sv_respawn_delay_small 0 +set g_sv_respawn_delay_small_count 0 +set g_sv_respawn_delay_large 0 +set g_sv_respawn_delay_large_count 0 +set g_sv_respawn_delay_max 0 +set g_sv_respawn_waves 0 +set g_sv_weapon_stay 0 // ========= @@@ -233,7 -224,7 +233,7 @@@ set g_ca_point_limit -1 "Clan Arena poi set g_ca_point_leadlimit -1 "Clan Arena point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" set g_ca_spectate_enemies 0 "allow eliminated players to spectate enemy players during Clan Arena games" set g_ca_warmup 10 "time players get to run around before the round starts" - set g_ca_damage2score_multiplier 0.01 + set g_ca_damage2score 100 "every this amount of damage done give players 1 point" set g_ca_round_timelimit 180 "round time limit in seconds" set g_ca_teams_override 0 set g_ca_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any" @@@ -331,7 -322,7 +331,7 @@@ exec ctfscoring-samual.cf // ==================== set g_cts 0 "CTS: complete the stage" set g_cts_selfdamage 1 "0 = disable all selfdamage and falldamage in cts" - set g_cts_finish_kill_delay 10 "prevent cheating by running back to the start line, and starting out with more speed than otherwise possible" + set g_cts_finish_kill_delay 2 "kill player this many seconds after stage completion to prevent cheating by starting out with more speed than otherwise possible; set it to 0 to not kill or to -1 to kill instantly" set g_cts_send_rankings_cnt 15 "send this number of map records to clients" set g_cts_removeprojectiles 0 "remove projectiles when the player dies, to prevent using weapons earlier in the stage than intended" @@@ -454,10 -445,13 +454,13 @@@ set g_keyhunt_team_spawns 0 "when 1, pl set g_lms 0 "Last Man Standing: everyone starts with a certain amount of lives, and the survivor wins" set g_lms_lives_override -1 set g_lms_extra_lives 0 - set g_lms_regenerate 0 - set g_lms_last_join 3 "if g_lms_join_anytime is false, new players can only join if the worst active player has more than (fraglimit - g_lms_last_join) lives" - set g_lms_join_anytime 1 "if true, new players can join, but get same amount of lives as the worst player" + set g_lms_regenerate 0 "health and/or armor regeneration, according to g_balance_health_regen and g_balance_armor_regen" + set g_lms_rot 0 "health and/or armor rotting, according to g_balance_health_rot and g_balance_armor_rot" + set g_lms_last_join 3 "if g_lms_join_anytime is 0, new players can only join if the worst active player has (fraglimit - g_lms_last_join) or more lives; in other words, new players can no longer join once the worst player loses more than g_lms_last_join lives" + set g_lms_join_anytime 1 "1: new players can join, but get same amount of lives as the worst player; 0: new players can only join if the worst active player has (fraglimit - g_lms_last_join) or more lives" + set g_lms_items 0 "enables items to spawn, weaponarena still disables weapons and ammo (to force all items to spawn, use g_pickup_items 1 instead)" set g_lms_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena" + set g_lms_forfeit_min_match_time 30 "end the match early if at least this many seconds have elapsed and less than 2 players are playing due to forfeits" // ========= @@@ -557,8 -551,6 +560,6 @@@ set g_invasion_monster_count 10 "numbe set g_invasion_zombies_only 0 "only spawn zombies" set g_invasion_spawn_delay 0.25 set g_invasion_spawnpoint_spawn_delay 0.5 - set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)" - set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode" set g_invasion_type 0 "type of invasion mode - 0: round-based, 1: hunting, 2: complete the stage (note: use mapinfo to set this)" // ====== @@@ -568,14 -560,3 +569,14 @@@ set g_duel 0 "Duel: frag the opponent m //set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel" set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode" set g_duel_not_dm_maps 0 "when this is set, DM maps will NOT be listed in duel" + +// ========== +// survival +// ========== +set g_survival 0 "Survival: identify and eliminate all the hunters before all your allies are gone" +set g_survival_not_lms_maps 0 "when this is set, LMS maps will NOT be listed in survival" +set g_survival_hunter_count 0.25 "number of players who will become hunters, set between 0 and 0.9 to use a multiplier of the current players, or 1 and above to specify an exact number of players" +set g_survival_punish_teamkill 1 "kill the player when they kill an ally" +set g_survival_reward_survival 1 "give a point to all surviving players if the round timelimit is reached, in addition to the points given for kills" +set g_survival_warmup 10 "how long the players will have time to run around the map before the round starts" +set g_survival_round_timelimit 180 "round time limit in seconds" diff --combined notifications.cfg index 88a6828d3,afae1593f..46d976e99 --- a/notifications.cfg +++ b/notifications.cfg @@@ -162,10 -162,10 +162,10 @@@ seta notification_INFO_DEATH_SELF_FALL seta notification_INFO_DEATH_SELF_FIRE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_GENERIC "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_LAVA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_DEATH_SELF_MON_GOLEM_CLAW "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_DEATH_SELF_MON_GOLEM_SMASH "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" + seta notification_INFO_DEATH_SELF_MON_GOLEM_ZAP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_MON_MAGE "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" - seta notification_INFO_DEATH_SELF_MON_SHAMBLER_CLAW "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" - seta notification_INFO_DEATH_SELF_MON_SHAMBLER_SMASH "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" - seta notification_INFO_DEATH_SELF_MON_SHAMBLER_ZAP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_MON_SPIDER "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_MON_WYVERN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_DEATH_SELF_MON_ZOMBIE_JUMP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" @@@ -274,8 -274,6 +274,8 @@@ seta notification_INFO_SCORES "1" "0 = seta notification_INFO_SPECTATE_WARNING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_SUPERSPEC_MISSING_UID "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_SUPERWEAPON_PICKUP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" +seta notification_INFO_SURVIVAL_HUNTER_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" +seta notification_INFO_SURVIVAL_SUVIVOR_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_TEAMCHANGE_LARGERTEAM "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_TEAMCHANGE_NOTALLOWED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" seta notification_INFO_VERSION_BETA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)" @@@ -349,7 -347,6 +349,6 @@@ seta notification_CENTER_ALONE "1" "0 seta notification_CENTER_ASSAULT_ATTACKING "1" "0 = off, 1 = centerprint" seta notification_CENTER_ASSAULT_DEFENDING "1" "0 = off, 1 = centerprint" seta notification_CENTER_ASSAULT_OBJ_DESTROYED "1" "0 = off, 1 = centerprint" - seta notification_CENTER_CAMPAIGN_MESSAGE "1" "0 = off, 1 = centerprint" seta notification_CENTER_CAMPCHECK "1" "0 = off, 1 = centerprint" seta notification_CENTER_COINTOSS "1" "0 = off, 1 = centerprint" seta notification_CENTER_COUNTDOWN_BEGIN "1" "0 = off, 1 = centerprint" @@@ -482,6 -479,7 +481,7 @@@ seta notification_CENTER_KEYHUNT_ROUNDS seta notification_CENTER_KEYHUNT_SCAN "1" "0 = off, 1 = centerprint" seta notification_CENTER_KEYHUNT_START "1" "0 = off, 1 = centerprint" seta notification_CENTER_LMS_NOLIVES "1" "0 = off, 1 = centerprint" + seta notification_CENTER_LMS_SPECWARN "1" "0 = off, 1 = centerprint" seta notification_CENTER_MISSING_PLAYERS "1" "0 = off, 1 = centerprint" seta notification_CENTER_MISSING_TEAMS "1" "0 = off, 1 = centerprint" seta notification_CENTER_MOTD "1" "0 = off, 1 = centerprint" @@@ -526,10 -524,6 +526,10 @@@ seta notification_CENTER_SEQUENCE_COUNT seta notification_CENTER_SUPERWEAPON_BROKEN "1" "0 = off, 1 = centerprint" seta notification_CENTER_SUPERWEAPON_LOST "1" "0 = off, 1 = centerprint" seta notification_CENTER_SUPERWEAPON_PICKUP "1" "0 = off, 1 = centerprint" +seta notification_CENTER_SURVIVAL_HUNTER "1" "0 = off, 1 = centerprint" +seta notification_CENTER_SURVIVAL_HUNTER_WIN "1" "0 = off, 1 = centerprint" +seta notification_CENTER_SURVIVAL_SURVIVOR "1" "0 = off, 1 = centerprint" +seta notification_CENTER_SURVIVAL_SURVIVOR_WIN "1" "0 = off, 1 = centerprint" seta notification_CENTER_TEAMCHANGE_AUTO "1" "0 = off, 1 = centerprint" seta notification_CENTER_TEAMCHANGE "1" "0 = off, 1 = centerprint" seta notification_CENTER_TEAMCHANGE_SPECTATE "1" "0 = off, 1 = centerprint" @@@ -584,10 -578,10 +584,10 @@@ seta notification_DEATH_SELF_FALL "1" " seta notification_DEATH_SELF_FIRE "1" "Enable this multiple notification" seta notification_DEATH_SELF_GENERIC "1" "Enable this multiple notification" seta notification_DEATH_SELF_LAVA "1" "Enable this multiple notification" + seta notification_DEATH_SELF_MON_GOLEM_CLAW "1" "Enable this multiple notification" + seta notification_DEATH_SELF_MON_GOLEM_SMASH "1" "Enable this multiple notification" + seta notification_DEATH_SELF_MON_GOLEM_ZAP "1" "Enable this multiple notification" seta notification_DEATH_SELF_MON_MAGE "1" "Enable this multiple notification" - seta notification_DEATH_SELF_MON_SHAMBLER_CLAW "1" "Enable this multiple notification" - seta notification_DEATH_SELF_MON_SHAMBLER_SMASH "1" "Enable this multiple notification" - seta notification_DEATH_SELF_MON_SHAMBLER_ZAP "1" "Enable this multiple notification" seta notification_DEATH_SELF_MON_SPIDER "1" "Enable this multiple notification" seta notification_DEATH_SELF_MON_WYVERN "1" "Enable this multiple notification" seta notification_DEATH_SELF_MON_ZOMBIE_JUMP "1" "Enable this multiple notification" diff --combined qcsrc/common/ent_cs.qc index 7bc6aa525,536637145..9ec68acb8 --- a/qcsrc/common/ent_cs.qc +++ b/qcsrc/common/ent_cs.qc @@@ -2,12 -2,12 +2,12 @@@ #if defined(CSQC) #include - #include + #include #elif defined(MENUQC) #elif defined(SVQC) #include - #include - #include + #include + #include #endif REGISTRY(EntCSProps, BITS(16) - 1) @@@ -157,11 -157,6 +157,11 @@@ ENTCS_PROP(SOLID, true, sv_solid, solid { WriteByte(chan, ent.sv_solid); }, { ent.sv_solid = ReadByte(); }) +// gamemode specific player survival status (independent of score and frags) +ENTCS_PROP(SURVIVAL_STATUS, true, survival_status, survival_status, ENTCS_SET_NORMAL, + { WriteShort(chan, ent.survival_status); }, + { ent.survival_status = ReadShort(); }) + #ifdef SVQC int ENTCS_PUBLICMASK = 0, ENTCS_PRIVATEMASK = 0; @@@ -193,7 -188,7 +193,7 @@@ { if (radar_showenemies) break; if (SAME_TEAM(to, player)) break; - if (!(IS_PLAYER(to) || to.caplayer)) break; + if (!(IS_PLAYER(to) || INGAME(to))) break; } sf &= ENTCS_PUBLICMASK; // no private updates } while (0); diff --combined qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc index ec7c40f8d,000000000..aa3631af9 mode 100644,000000..100644 --- a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc +++ b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc @@@ -1,459 -1,0 +1,453 @@@ +#include "sv_survival.qh" + +float autocvar_g_survival_hunter_count = 0.25; +float autocvar_g_survival_round_timelimit = 180; +float autocvar_g_survival_warmup = 10; +bool autocvar_g_survival_punish_teamkill = true; +bool autocvar_g_survival_reward_survival = true; + +void surv_FakeTimeLimit(entity e, float t) +{ + if(!IS_REAL_CLIENT(e)) + return; +#if 0 + msg_entity = e; + WriteByte(MSG_ONE, 3); // svc_updatestat + WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT + if(t < 0) + WriteCoord(MSG_ONE, autocvar_timelimit); + else + WriteCoord(MSG_ONE, (t + 1) / 60); +#else + STAT(SURVIVAL_ROUNDTIMER, e) = t; +#endif +} + +void nades_Clear(entity player); + +void Surv_UpdateScores(bool timed_out) +{ + // give players their hard-earned kills now that the round is over + FOREACH_CLIENT(true, + { + it.totalfrags += it.survival_validkills; + if(it.survival_validkills) + GameRules_scoring_add(it, SCORE, it.survival_validkills); + it.survival_validkills = 0; + // player survived the round + if(IS_PLAYER(it) && !IS_DEAD(it)) + { + if(autocvar_g_survival_reward_survival && timed_out && it.survival_status == SV_STATUS_PREY) + GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit + if(it.survival_status == SV_STATUS_PREY) + GameRules_scoring_add(it, SV_SURVIVALS, 1); + else if(it.survival_status == SV_STATUS_HUNTER) + GameRules_scoring_add(it, SV_HUNTS, 1); + } + }); +} + +float Surv_CheckWinner() +{ + if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0) + { + // if the match times out, survivors win too! + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN); + FOREACH_CLIENT(true, + { + if(IS_PLAYER(it)) + nades_Clear(it); + surv_FakeTimeLimit(it, -1); + }); + + Surv_UpdateScores(true); + + allowed_to_spawn = false; + game_stopped = true; + round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit); + return 1; + } + + int survivor_count = 0, hunter_count = 0; + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), + { + if(it.survival_status == SV_STATUS_PREY) + survivor_count++; + else if(it.survival_status == SV_STATUS_HUNTER) + hunter_count++; + }); + if(survivor_count > 0 && hunter_count > 0) + { + return 0; + } + + if(hunter_count > 0) // hunters win + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN); + } + else if(survivor_count > 0) // survivors win + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN); + } + else + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED); + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED); + } + + Surv_UpdateScores(false); + + allowed_to_spawn = false; + game_stopped = true; + round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit); + + FOREACH_CLIENT(true, + { + if(IS_PLAYER(it)) + nades_Clear(it); + surv_FakeTimeLimit(it, -1); + }); + + return 1; +} + +void Surv_RoundStart() +{ + allowed_to_spawn = boolean(warmup_stage); + int playercount = 0; + FOREACH_CLIENT(true, + { + if(IS_PLAYER(it) && !IS_DEAD(it)) + { + ++playercount; + it.survival_status = SV_STATUS_PREY; + } + else + it.survival_status = 0; // this is mostly a safety check; if a client manages to somehow maintain a survival status, clear it before the round starts! + it.survival_validkills = 0; + }); + int hunter_count = bound(1, ((autocvar_g_survival_hunter_count >= 1) ? autocvar_g_survival_hunter_count : floor(playercount * autocvar_g_survival_hunter_count)), playercount - 1); // 20%, but ensure at least 1 and less than total + int total_hunters = 0; + FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it), + { + if(total_hunters >= hunter_count) + break; + total_hunters++; + it.survival_status = SV_STATUS_HUNTER; + }); + + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), + { + if(it.survival_status == SV_STATUS_PREY) + Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR); + else if(it.survival_status == SV_STATUS_HUNTER) + Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER); + + surv_FakeTimeLimit(it, round_handler_GetEndTime()); + }); +} + +bool Surv_CheckPlayers() +{ + static int prev_missing_players; + allowed_to_spawn = true; + int playercount = 0; + FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), + { + ++playercount; + }); + if (playercount >= 2) + { + if(prev_missing_players > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS); + prev_missing_players = -1; + return true; + } + if(playercount == 0) + { + if(prev_missing_players > 0) + Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS); + prev_missing_players = -1; + return false; + } + // if we get here, only 1 player is missing + if(prev_missing_players != 1) + { + Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1); + prev_missing_players = 1; + } + return false; +} + +bool surv_isEliminated(entity e) +{ - if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME)) ++ if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME)) + return true; - if(e.caplayer == 0.5) ++ if(INGAME_JOINING(e)) + return true; + return false; +} + +void surv_Initialize() // run at the start of a match, initiates game mode +{ + GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, { + field(SP_SV_SURVIVALS, "survivals", 0); + field(SP_SV_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY); + }); + + allowed_to_spawn = true; + round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart); + round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit); + EliminatedPlayers_Init(surv_isEliminated); +} + + +// ============== +// Hook Functions +// ============== + +MUTATOR_HOOKFUNCTION(sv, ClientObituary) +{ + // in survival, announcing a frag would tell everyone who the hunter is + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target) + { + float frag_deathtype = M_ARGV(3, float); + entity wep_ent = M_ARGV(4, entity); + // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one + // unless the player is going to be punished for suicide, in which case just remove one + if(frag_attacker.survival_status == frag_target.survival_status) + GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld); + } + + M_ARGV(5, bool) = true; // anonymous attacker +} + +MUTATOR_HOOKFUNCTION(sv, PlayerPreThink) +{ + entity player = M_ARGV(0, entity); + - if(IS_PLAYER(player) || player.caplayer) ++ if(IS_PLAYER(player) || INGAME_JOINED(player)) + { + // update the scoreboard colour display to out the real killer at the end of the round + // running this every frame to avoid cheats + int plcolor = SV_COLOR_PREY; + if(player.survival_status == SV_STATUS_HUNTER && game_stopped) + plcolor = SV_COLOR_HUNTER; + setcolor(player, plcolor); + } +} + +MUTATOR_HOOKFUNCTION(sv, PlayerSpawn) +{ + entity player = M_ARGV(0, entity); + + player.survival_status = 0; + player.survival_validkills = 0; - player.caplayer = 1; ++ INGAME_STATUS_SET(player, INGAME_STATUS_JOINED); + if (!warmup_stage) + eliminatedPlayers.SendFlags |= 1; +} + +MUTATOR_HOOKFUNCTION(sv, ForbidSpawn) +{ + entity player = M_ARGV(0, entity); + + // spectators / observers that weren't playing can join; they are + // immediately forced to observe in the PutClientInServer hook + // this way they are put in a team and can play in the next round - if (!allowed_to_spawn && player.caplayer) ++ if (!allowed_to_spawn && INGAME(player)) + return true; + return false; +} + +MUTATOR_HOOKFUNCTION(sv, PutClientInServer) +{ + entity player = M_ARGV(0, entity); + + if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join + { + TRANSMUTE(Observer, player); - if (CS(player).jointime != time && !player.caplayer) // not when connecting ++ if (CS(player).jointime != time && !INGAME(player)) // not when connecting + { - player.caplayer = 0.5; ++ INGAME_STATUS_SET(player, INGAME_STATUS_JOINING); + Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE); + } + } +} + +MUTATOR_HOOKFUNCTION(sv, reset_map_players) +{ + FOREACH_CLIENT(true, { + CS(it).killcount = 0; + it.survival_status = 0; + surv_FakeTimeLimit(it, -1); // restore original timelimit - if (!it.caplayer && IS_BOT_CLIENT(it)) - it.caplayer = 1; - if (it.caplayer) ++ if (INGAME(it) || IS_BOT_CLIENT(it)) + { + TRANSMUTE(Player, it); - it.caplayer = 1; ++ INGAME_STATUS_SET(it, INGAME_STATUS_JOINED); + PutClientInServer(it); + } + }); + bot_relinkplayerlist(); + return true; +} + +MUTATOR_HOOKFUNCTION(sv, reset_map_global) +{ + allowed_to_spawn = true; + return true; +} + +entity surv_LastPlayerForTeam(entity this) +{ + entity last_pl = NULL; + FOREACH_CLIENT(IS_PLAYER(it) && it != this, { + if (!IS_DEAD(it) && this.survival_status == it.survival_status) + { + if (!last_pl) + last_pl = it; + else + return NULL; + } + }); + return last_pl; +} + +void surv_LastPlayerForTeam_Notify(entity this) +{ + if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) + { + entity pl = surv_LastPlayerForTeam(this); + if (pl) + Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE); + } +} + +MUTATOR_HOOKFUNCTION(sv, PlayerDies) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + + surv_LastPlayerForTeam_Notify(frag_target); + if (!allowed_to_spawn) + { + frag_target.respawn_flags = RESPAWN_SILENT; + // prevent unwanted sudden rejoin as spectator and movement of spectator camera + frag_target.respawn_time = time + 2; + } + frag_target.respawn_flags |= RESPAWN_FORCE; + if (!warmup_stage) + { + eliminatedPlayers.SendFlags |= 1; + if (IS_BOT_CLIENT(frag_target)) + bot_clearqueue(frag_target); + } + + // killed an ally! punishment is death + if(autocvar_g_survival_punish_teamkill && frag_attacker != frag_target && IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker.survival_status == frag_target.survival_status && !ITEM_DAMAGE_NEEDKILL(frag_deathtype)) + if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't + Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0'); + return true; +} + +MUTATOR_HOOKFUNCTION(sv, ClientDisconnect) +{ + entity player = M_ARGV(0, entity); + + if (IS_PLAYER(player) && !IS_DEAD(player)) + surv_LastPlayerForTeam_Notify(player); + return true; +} + +MUTATOR_HOOKFUNCTION(sv, MakePlayerObserver) +{ + entity player = M_ARGV(0, entity); ++ bool is_forced = M_ARGV(1, bool); ++ if (is_forced && INGAME(player)) ++ INGAME_STATUS_CLEAR(player); + + if (IS_PLAYER(player) && !IS_DEAD(player)) + surv_LastPlayerForTeam_Notify(player); + if (player.killindicator_teamchange == -2) // player wants to spectate - player.caplayer = 0; - if (player.caplayer) ++ INGAME_STATUS_CLEAR(player); ++ if (INGAME(player)) + player.frags = FRAGS_PLAYER_OUT_OF_GAME; + if (!warmup_stage) + eliminatedPlayers.SendFlags |= 1; - if (!player.caplayer) ++ if (!INGAME(player)) + { + player.survival_validkills = 0; + player.survival_status = 0; + surv_FakeTimeLimit(player, -1); // restore original timelimit + return false; // allow team reset + } + return true; // prevent team reset +} + +MUTATOR_HOOKFUNCTION(sv, Scores_CountFragsRemaining) +{ + // announce remaining frags? + return true; +} + +MUTATOR_HOOKFUNCTION(sv, GiveFragsForKill, CBC_ORDER_FIRST) +{ + entity frag_attacker = M_ARGV(0, entity); + if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) + frag_attacker.survival_validkills += M_ARGV(2, float); + M_ARGV(2, float) = 0; // score will be given to the winner when the round ends + return true; +} + +MUTATOR_HOOKFUNCTION(sv, AddPlayerScore) +{ + entity scorefield = M_ARGV(0, entity); + if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN) + M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter! +} + +MUTATOR_HOOKFUNCTION(sv, CalculateRespawnTime) +{ + // no respawn calculations needed, player is forced to spectate anyway + return true; +} + +MUTATOR_HOOKFUNCTION(sv, Bot_FixCount, CBC_ORDER_EXCLUSIVE) +{ + FOREACH_CLIENT(IS_REAL_CLIENT(it), { - if (IS_PLAYER(it) || it.caplayer == 1) ++ if (IS_PLAYER(it) || INGAME_JOINED(it)) + ++M_ARGV(0, int); + ++M_ARGV(1, int); + }); + return true; +} + +MUTATOR_HOOKFUNCTION(sv, ClientCommand_Spectate) +{ + entity player = M_ARGV(0, entity); + - if (player.caplayer) ++ if (INGAME(player)) + { + // they're going to spec, we can do other checks + if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player))) + Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE); + return MUT_SPECCMD_FORCE; + } + + return MUT_SPECCMD_CONTINUE; +} + - MUTATOR_HOOKFUNCTION(sv, GetPlayerStatus) - { - entity player = M_ARGV(0, entity); - - return player.caplayer == 1; - } - +MUTATOR_HOOKFUNCTION(sv, BotShouldAttack) +{ + entity bot = M_ARGV(0, entity); + entity targ = M_ARGV(1, entity); + + if(targ.survival_status == bot.survival_status) + return true; +} diff --combined qcsrc/common/notifications/all.inc index 8204e7930,87398b11d..2f9bc7557 --- a/qcsrc/common/notifications/all.inc +++ b/qcsrc/common/notifications/all.inc @@@ -300,9 -300,9 +300,9 @@@ string multiteam_info_sprintf(string in MSG_INFO_NOTIF(DEATH_SELF_GENERIC, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_selfkill", _("^BG%s^K1 died%s%s"), "") MSG_INFO_NOTIF(DEATH_SELF_LAVA, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_lava", _("^BG%s^K1 turned into hot slag%s%s"), _("^BG%s^K1 found a hot place%s%s")) MSG_INFO_NOTIF(DEATH_SELF_MON_MAGE, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was exploded by a Mage%s%s"), "") - MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1's innards became outwards by a Shambler%s%s"), "") - MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was smashed by a Shambler%s%s"), "") - MSG_INFO_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was zapped to death by a Shambler%s%s"), "") + MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_CLAW, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1's innards became outwards by a Golem%s%s"), "") + MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_SMASH, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was smashed by a Golem%s%s"), "") + MSG_INFO_NOTIF(DEATH_SELF_MON_GOLEM_ZAP, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was zapped to death by a Golem%s%s"), "") MSG_INFO_NOTIF(DEATH_SELF_MON_SPIDER, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was bitten by a Spider%s%s"), "") MSG_INFO_NOTIF(DEATH_SELF_MON_WYVERN, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 was fireballed by a Wyvern%s%s"), "") MSG_INFO_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP, N_CONSOLE, 2, 1, "s1 s2loc spree_lost", "s1", "notify_death", _("^BG%s^K1 joins the Zombies%s%s"), "") @@@ -432,9 -432,6 +432,9 @@@ MSG_INFO_NOTIF(SUPERWEAPON_PICKUP, N_CONSOLE, 1, 0, "s1", "s1", "superweapons", _("^BG%s^K1 picked up a Superweapon"), "") + MSG_INFO_NOTIF(SURVIVAL_HUNTER_WIN, N_CONSOLE, 0, 0, "", "", "", _("^K1Hunters^BG win the round"), "") + MSG_INFO_NOTIF(SURVIVAL_SURVIVOR_WIN, N_CONSOLE, 0, 0, "", "", "", _("^F1Survivors^BG win the round"), "") + MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM, N_CONSOLE, 0, 0, "", "", "", _("^BGYou cannot change to a larger team"), "") MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED, N_CONSOLE, 0, 0, "", "", "", _("^BGYou are not allowed to change teams"), "") @@@ -529,9 -526,9 +529,9 @@@ MSG_CENTER_NOTIF(ASSAULT_DEFENDING, N_ENABLE, 0, 0, "", CPID_ASSAULT_ROLE, "0 0", _("^BGYou are defending!"), "") MSG_CENTER_NOTIF(ASSAULT_OBJ_DESTROYED, N_ENABLE, 0, 1, "f1time", CPID_ASSAULT_ROLE, "0 0", _("^BGObjective destroyed in ^F4%s^BG!"), "") - MSG_CENTER_NOTIF(COUNTDOWN_BEGIN, N_ENABLE, 0, 0, "", CPID_ROUND, "2 0", _("^F4Begin!"), "") - MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART, N_ENABLE, 0, 1, "", CPID_ROUND, "1 f1", _("^F4Game starts in ^COUNT"), "") - MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART, N_ENABLE, 0, 1, "", CPID_ROUND, "1 f1", _("^F4Round starts in ^COUNT"), "") + MSG_CENTER_NOTIF(COUNTDOWN_BEGIN, N_ENABLE, 0, 0, "", CPID_ROUND, "2 0", BOLD(_("^BGBegin!")), "") + MSG_CENTER_NOTIF(COUNTDOWN_GAMESTART, N_ENABLE, 0, 1, "", CPID_ROUND, "1 f1", strcat(_("^BGGame starts in"), "\n", BOLD("^COUNT")), "") + MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTART, N_ENABLE, 0, 2, "f1", CPID_ROUND, "1 f2", strcat(_("^BGRound %s starts in"), "\n", BOLD("^COUNT")), "") MSG_CENTER_NOTIF(COUNTDOWN_ROUNDSTOP, N_ENABLE, 0, 0, "", CPID_ROUND, "2 0", _("^F4Round cannot start"), "") MSG_CENTER_NOTIF(ROUND_TIED, N_ENABLE, 0, 0, "", CPID_ROUND, "0 0", _("^BGRound tied"), "") @@@ -706,6 -703,7 +706,7 @@@ MULTITEAM_CENTER(KEYHUNT_START, N_ENABLE, 0, 0, "", CPID_KEYHUNT, "0 0", _("^BGYou are starting with the ^TC^TT Key"), "", KEY) MSG_CENTER_NOTIF(LMS_NOLIVES, N_ENABLE, 0, 0, "", CPID_LMS, "0 0", _("^BGYou have no lives left, you must wait until the next match"), "") + MSG_CENTER_NOTIF(LMS_SPECWARN, N_ENABLE, 0, 0, "", CPID_LMS, "0 0", _("^F4WARNING:^BG you can't rejoin this match after spectating.\nUse the same command again to spectate anyway."), "") MSG_CENTER_NOTIF(MISSING_TEAMS, N_ENABLE, 0, 1, "missing_teams", CPID_MISSING_TEAMS, "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") MSG_CENTER_NOTIF(MISSING_PLAYERS, N_ENABLE, 0, 1, "f1", CPID_MISSING_PLAYERS, "-1 0", _("^BGWaiting for %s player(s) to join..."), "") @@@ -715,7 -713,6 +716,6 @@@ MSG_CENTER_NOTIF(INSTAGIB_FINDAMMO_FIRST, N_ENABLE, 0, 0, "", CPID_INSTAGIB_FINDAMMO, "1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!")) MSG_CENTER_NOTIF(INSTAGIB_LIVES_REMAINING, N_ENABLE, 0, 1, "f1", CPID_Null, "0 0", _("^F2Extra lives remaining: ^K1%s"), "") - MSG_CENTER_NOTIF(CAMPAIGN_MESSAGE, N_ENABLE, 1, 1, "f1 s1 join_key", CPID_CAMPAIGN_MESSAGE, "-1 0", strcat(_("Level %s: "), "^BG%s\n^3\n", _("^BGPress ^F2%s^BG to enter the game")), "") MSG_CENTER_NOTIF(MOTD, N_ENABLE, 1, 0, "s1", CPID_MOTD, "-1 0", "^BG%s", "") MSG_CENTER_NOTIF(NIX_COUNTDOWN, N_ENABLE, 0, 2, "item_wepname", CPID_NIX, "1 f2", _("^F2^COUNT^BG until weapon change...\nNext weapon: ^F1%s"), "") @@@ -762,11 -759,6 +762,11 @@@ MSG_CENTER_NOTIF(SUPERWEAPON_LOST, N_ENABLE, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Superweapons have been lost"), "") MSG_CENTER_NOTIF(SUPERWEAPON_PICKUP, N_ENABLE, 0, 0, "", CPID_POWERUP, "0 0", _("^F2You now have a superweapon"), "") + MSG_CENTER_NOTIF(SURVIVAL_HUNTER, N_ENABLE, 0, 0, "", CPID_SURVIVAL, "5 0", strcat(BOLD_OPERATOR, _("^BGYou are a ^K1hunter^BG! Eliminate the survivor(s) without raising suspicion!")), "") + MSG_CENTER_NOTIF(SURVIVAL_HUNTER_WIN, N_ENABLE, 0, 0, "", CPID_ROUND, "0 0", _("^K1Hunters^BG win the round"), "") + MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR, N_ENABLE, 0, 0, "", CPID_SURVIVAL, "5 0", strcat(BOLD_OPERATOR, _("^BGYou are a ^F1survivor^BG! Identify and eliminate the hunter(s)!")), "") + MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR_WIN, N_ENABLE, 0, 0, "", CPID_ROUND, "0 0", _("^F1Survivors^BG win the round"), "") + MULTITEAM_CENTER(TEAMCHANGE, N_ENABLE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "", NAME) MSG_CENTER_NOTIF(TEAMCHANGE_AUTO, N_ENABLE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Changing team in ^COUNT"), "") MSG_CENTER_NOTIF(TEAMCHANGE_SPECTATE, N_ENABLE, 0, 1, "", CPID_TEAMCHANGE, "1 f1", _("^K1Spectating in ^COUNT"), "") @@@ -842,9 -834,9 +842,9 @@@ MSG_MULTI_NOTIF(DEATH_SELF_GENERIC, N_ENABLE, NULL, INFO_DEATH_SELF_GENERIC, CENTER_DEATH_SELF_GENERIC) MSG_MULTI_NOTIF(DEATH_SELF_LAVA, N_ENABLE, NULL, INFO_DEATH_SELF_LAVA, CENTER_DEATH_SELF_LAVA) MSG_MULTI_NOTIF(DEATH_SELF_MON_MAGE, N_ENABLE, NULL, INFO_DEATH_SELF_MON_MAGE, CENTER_DEATH_SELF_MONSTER) - MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_CLAW, N_ENABLE, NULL, INFO_DEATH_SELF_MON_SHAMBLER_CLAW, CENTER_DEATH_SELF_MONSTER) - MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_SMASH, N_ENABLE, NULL, INFO_DEATH_SELF_MON_SHAMBLER_SMASH, CENTER_DEATH_SELF_MONSTER) - MSG_MULTI_NOTIF(DEATH_SELF_MON_SHAMBLER_ZAP, N_ENABLE, NULL, INFO_DEATH_SELF_MON_SHAMBLER_ZAP, CENTER_DEATH_SELF_MONSTER) + MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_CLAW, N_ENABLE, NULL, INFO_DEATH_SELF_MON_GOLEM_CLAW, CENTER_DEATH_SELF_MONSTER) + MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_SMASH, N_ENABLE, NULL, INFO_DEATH_SELF_MON_GOLEM_SMASH, CENTER_DEATH_SELF_MONSTER) + MSG_MULTI_NOTIF(DEATH_SELF_MON_GOLEM_ZAP, N_ENABLE, NULL, INFO_DEATH_SELF_MON_GOLEM_ZAP, CENTER_DEATH_SELF_MONSTER) MSG_MULTI_NOTIF(DEATH_SELF_MON_SPIDER, N_ENABLE, NULL, INFO_DEATH_SELF_MON_SPIDER, CENTER_DEATH_SELF_MONSTER) MSG_MULTI_NOTIF(DEATH_SELF_MON_WYVERN, N_ENABLE, NULL, INFO_DEATH_SELF_MON_WYVERN, CENTER_DEATH_SELF_MONSTER) MSG_MULTI_NOTIF(DEATH_SELF_MON_ZOMBIE_JUMP, N_ENABLE, NULL, INFO_DEATH_SELF_MON_ZOMBIE_JUMP, CENTER_DEATH_SELF_MONSTER) diff --combined qcsrc/common/notifications/all.qh index e1969ef5c,fb06d264a..e16512513 --- a/qcsrc/common/notifications/all.qh +++ b/qcsrc/common/notifications/all.qh @@@ -62,7 -62,6 +62,6 @@@ ENUMCLASS(CPID CASE(CPID, MISSING_TEAMS) CASE(CPID, MISSING_PLAYERS) CASE(CPID, INSTAGIB_FINDAMMO) - CASE(CPID, CAMPAIGN_MESSAGE) CASE(CPID, MOTD) CASE(CPID, NIX) CASE(CPID, ONSLAUGHT) @@@ -70,7 -69,6 +69,7 @@@ CASE(CPID, OVERTIME) CASE(CPID, POWERUP) CASE(CPID, RACE_FINISHLAP) + CASE(CPID, SURVIVAL) CASE(CPID, TEAMCHANGE) CASE(CPID, TIMEOUT) CASE(CPID, TIMEIN) @@@ -360,8 -358,8 +359,8 @@@ float autocvar_notification_show_sprees f1points: point or points depending on f1 f1ord: count_ordinal of f1 f1time: process_time of f1 - f1race_time: mmssss of f1 - f2race_time: mmssss of f2 + f1race_time: TIME_ENCODED_TOSTRING of f1 + f2race_time: TIME_ENCODED_TOSTRING of f2 race_col: color of race time/position (i.e. good or bad) race_diff: show time difference between f2 and f3 missing_teams: show which teams still need players @@@ -420,11 -418,11 +419,11 @@@ string BUFF_NAME(int i) ARG_CASE(ARG_CS, "f1points", (f1 == 1 ? _("point") : _("points"))) \ ARG_CASE(ARG_CS_SV, "f1ord", count_ordinal(f1)) \ ARG_CASE(ARG_CS_SV, "f1time", process_time(2, f1)) \ - ARG_CASE(ARG_CS_SV_HA, "f1race_time", mmssss(f1)) \ - ARG_CASE(ARG_CS_SV_HA, "f2race_time", mmssss(f2)) \ - ARG_CASE(ARG_CS_SV_HA, "f3race_time", mmssss(f3)) \ + ARG_CASE(ARG_CS_SV_HA, "f1race_time", TIME_ENCODED_TOSTRING(f1, true)) \ + ARG_CASE(ARG_CS_SV_HA, "f2race_time", TIME_ENCODED_TOSTRING(f2, true)) \ + ARG_CASE(ARG_CS_SV_HA, "f3race_time", TIME_ENCODED_TOSTRING(f3, true)) \ ARG_CASE(ARG_CS_SV, "race_col", CCR(((f1 == 1) ? "^F1" : "^F2"))) \ - ARG_CASE(ARG_CS_SV, "race_diff", ((f2 > f3) ? sprintf(CCR("^1[+%s]"), mmssss(f2 - f3)) : sprintf(CCR("^2[-%s]"), mmssss(f3 - f2)))) \ + ARG_CASE(ARG_CS_SV, "race_diff", ((f2 > f3) ? sprintf(CCR("^1[+%s]"), TIME_ENCODED_TOSTRING(f2 - f3, true)) : sprintf(CCR("^2[-%s]"), TIME_ENCODED_TOSTRING(f3 - f2, true)))) \ ARG_CASE(ARG_CS, "missing_teams", notif_arg_missing_teams(f1)) \ ARG_CASE(ARG_CS, "pass_key", getcommandkey(_("drop flag"), "+use")) \ ARG_CASE(ARG_CS, "nade_key", getcommandkey(_("throw nade"), "dropweapon")) \ @@@ -635,6 -633,7 +634,7 @@@ string notif_arg_item_wepammo(float f1 { string ammoitems = ""; Weapon wep = REGISTRY_GET(Weapons, f1); + // TODO: registry handles switch (wep.ammo_type) { case RES_SHELLS: ammoitems = ITEM_Shells.m_name; break; diff --combined qcsrc/common/scores.qh index 002a6a3c0,45af93992..9e570e30e --- a/qcsrc/common/scores.qh +++ b/qcsrc/common/scores.qh @@@ -15,7 -15,6 +15,6 @@@ STATIC_INIT(Scores_renumber) { FOREACH( * Score indices */ - // game mode specific indices are not in common/, but in server/scores_rules.qc! #ifdef GAMEQC // fields not networked via the score system REGISTER_SP(END); @@@ -88,9 -87,6 +87,9 @@@ REGISTER_SP(NEXBALL_FAULTS) REGISTER_SP(ONS_TAKES); REGISTER_SP(ONS_CAPS); + +REGISTER_SP(SV_SURVIVALS); +REGISTER_SP(SV_HUNTS); #endif diff --combined qcsrc/common/stats.qh index 91e5631be,128f090c4..077767f2d --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@@ -79,6 -79,9 +79,9 @@@ float game_stopped float game_starttime; //point in time when the countdown to game start is over float round_starttime; //point in time when the countdown to round start is over int autocvar_leadlimit; + int overtimes; // overtimes added (-1 = sudden death) + int timeout_status; // (values: 0, 1, 2) contains whether a timeout is not active (0), was called but still at leadtime (1) or is active (2) + // TODO: world.qh can't be included here due to circular includes! #define autocvar_fraglimit cvar("fraglimit") #define autocvar_fraglimit_override cvar("fraglimit_override") @@@ -91,7 -94,6 +94,6 @@@ REGISTER_STAT(GAMESTARTTIME, float, gam /** arc heat in [0,1] */ REGISTER_STAT(PRESSED_KEYS, int) REGISTER_STAT(FUEL, int) - REGISTER_STAT(NB_METERSTART, float) /** compressShotOrigin */ REGISTER_STAT(SHOTORG, int) REGISTER_STAT(LEADLIMIT, float, autocvar_leadlimit) @@@ -100,7 -102,7 +102,7 @@@ REGISTER_STAT(LEADLIMIT_AND_FRAGLIMIT, REGISTER_STAT(LAST_PICKUP, float) REGISTER_STAT(HUD, int) REGISTER_STAT(HIT_TIME, float) - REGISTER_STAT(DAMAGE_DEALT_TOTAL, int) + REGISTER_STAT(HITSOUND_DAMAGE_DEALT_TOTAL, int) REGISTER_STAT(TYPEHIT_TIME, float) REGISTER_STAT(AIR_FINISHED, float) REGISTER_STAT(VEHICLESTAT_HEALTH, int) @@@ -116,6 -118,8 +118,8 @@@ REGISTER_STAT(SECRETS_TOTAL, int, secre REGISTER_STAT(SECRETS_FOUND, int, secrets_found) REGISTER_STAT(RESPAWN_TIME, float) REGISTER_STAT(ROUNDSTARTTIME, float, round_starttime) + REGISTER_STAT(OVERTIMES, int, overtimes) + REGISTER_STAT(TIMEOUT_STATUS, int, timeout_status) REGISTER_STAT(MONSTERS_TOTAL, int) REGISTER_STAT(MONSTERS_KILLED, int) REGISTER_STAT(NADE_BONUS, float) @@@ -338,6 -342,9 +342,9 @@@ REGISTER_STAT(DOM_PPS_BLUE, float REGISTER_STAT(DOM_PPS_YELLOW, float) REGISTER_STAT(DOM_PPS_PINK, float) + // nexball + REGISTER_STAT(NB_METERSTART, float) + #ifdef SVQC float autocvar_g_teleport_maxspeed; #endif @@@ -358,6 -365,8 +365,8 @@@ REGISTER_STAT(Q3COMPAT, int, q3compat #ifdef SVQC #include "physics/movetypes/movetypes.qh" float warmup_limit; + float round_limit; + int rounds_played; #endif #ifdef SVQC @@@ -397,6 -406,8 +406,8 @@@ REGISTER_STAT(MOVEVARS_AIRCONTROL, floa REGISTER_STAT(FRAGLIMIT, float, autocvar_fraglimit) REGISTER_STAT(TIMELIMIT, float, autocvar_timelimit) REGISTER_STAT(WARMUP_TIMELIMIT, float, warmup_limit) + REGISTER_STAT(ROUNDS_PLAYED, int, rounds_played) + REGISTER_STAT(ROUND_TIMELIMIT, float, round_limit) #ifdef SVQC float autocvar_sv_wallfriction; #define autocvar_sv_gravity cvar("sv_gravity") @@@ -428,5 -439,3 +439,5 @@@ REGISTER_STAT(GUNALIGN, int #ifdef SVQC SPECTATE_COPYFIELD(_STAT(GUNALIGN)) #endif + +REGISTER_STAT(SURVIVAL_ROUNDTIMER, float) diff --combined qcsrc/menu/xonotic/util.qc index 878106134,f98a8de88..f013263b6 --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@@ -313,11 -313,6 +313,6 @@@ void URI_Get_Callback(float id, float s } } - void DisableServerBackwardsCompatibility() - { - cvar_set("gameversion_min", ftos(100 * floor(cvar("gameversion") / 100))); - } - void UpdateNotification_URI_Get_Callback(float id, float status, string data) { float n; @@@ -352,6 -347,7 +347,7 @@@ string s; string un_version = ""; + string un_tosversion = ""; string un_download = ""; string un_url = ""; string un_bannedservers = ""; @@@ -372,6 -368,11 +368,11 @@@ un_version = s; break; } + case "T": + { + un_tosversion = s; + break; + } case "C": { un_compatexpire = s; @@@ -411,25 -412,17 +412,17 @@@ } } - if(un_version != "") + if(un_version != "" && vercmp(cvar_string("g_xonoticversion"), un_version) < 0) { - if(vercmp(cvar_string("g_xonoticversion"), un_version) < 0) - { - // update needed - _Nex_ExtResponseSystem_UpdateTo = strzone(un_version); - if(un_download) { LOG_INFO(_("Update can be downloaded at:"), "\n", un_download); } - if(un_url) { _Nex_ExtResponseSystem_UpdateToURL = strzone(un_url); } - DisableServerBackwardsCompatibility(); - } - else if(cvar_string("g_xonoticversion") == un_version) - { - if(un_compatexpire != "") - { - string curdate = strftime(false, "%Y%m%d%H%M%S"); - if (strcmp(curdate, un_compatexpire) >= 0) - DisableServerBackwardsCompatibility(); - } - } + // update needed + _Nex_ExtResponseSystem_UpdateTo = strzone(un_version); + if(un_download) { LOG_INFO(_("Update can be downloaded at:"), "\n", un_download); } + if(un_url) { _Nex_ExtResponseSystem_UpdateToURL = strzone(un_url); } + } + + if(un_tosversion != "") + { + _Nex_ExtResponseSystem_NewToS = stof(un_tosversion); } if(un_bannedservers != "") @@@ -464,15 -457,8 +457,8 @@@ void updateCheck( if(!_Nex_ExtResponseSystem_Queried) { _Nex_ExtResponseSystem_Queried = 1; - float startcnt; - string uri; - - cvar_set("cl_startcount", ftos(startcnt = cvar("cl_startcount") + 1)); - - // for privacy, munge the start count a little - startcnt = floor((floor(startcnt / 10) + random()) * 10); - uri = sprintf("http://update.xonotic.org/checkupdate.txt?version=%s&cnt=%d", uri_escape(cvar_string("g_xonoticversion")), startcnt); - uri_get(uri, URI_GET_UPDATENOTIFICATION); + cvar_set("cl_startcount", ftos(cvar("cl_startcount") + 1)); + uri_get("https://update.xonotic.org/checkupdate.txt", URI_GET_UPDATENOTIFICATION); } if(_Nex_ExtResponseSystem_PacksStep > 0) @@@ -582,6 -568,7 +568,7 @@@ void preMenuDraw( draw_CenterText(mid - 1 * line, l1, fs, '1 0 0', 1, 0); draw_CenterText(mid - 0 * line, l2, fs, '0 0 1', 1, 0); } + if (!campaign_name_previous) campaign_name_previous = strzone(strcat(campaign_name, "x")); // force unequal if(campaign_name == campaign_name_previous) @@@ -681,15 -668,14 +668,16 @@@ float updateCompression( GAMETYPE(MAPINFO_TYPE_NEXBALL) \ GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \ GAMETYPE(MAPINFO_TYPE_ASSAULT) \ ++ GAMETYPE(MAPINFO_TYPE_SURVIVAL) \ /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \ + /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \ - /* GAMETYPE(MAPINFO_TYPE_SURVIVAL) */ \ /**/ // hidden gametypes come last so indexing always works correctly #define HIDDEN_GAMETYPES \ GAMETYPE(MAPINFO_TYPE_RACE) \ GAMETYPE(MAPINFO_TYPE_CTS) \ + GAMETYPE(MAPINFO_TYPE_INVASION) \ /**/ Gametype GameType_GetID(int cnt) @@@ -818,6 -804,18 +806,18 @@@ void dialog_hudpanel_main_settings(enti e.configureXonoticTextSliderValues(e); } + bool isServerSingleplayer() + { + return (cvar_string("net_address") == "127.0.0.1" && cvar_string("net_address_ipv6") == "::1"); + } + + void makeServerSingleplayer() + { + // it doesn't allow clients to connect from different machines + localcmd("defer 0.1 \"sv_cmd settemp net_address 127.0.0.1\"\n"); + localcmd("defer 0.1 \"sv_cmd settemp net_address_ipv6 ::1\"\n"); + } + float getFadedAlpha(float currentAlpha, float startAlpha, float targetAlpha) { if(startAlpha < targetAlpha) diff --combined qcsrc/server/mutators/events.qh index f33fc4598,e7f9f897b..87b3aa16d --- a/qcsrc/server/mutators/events.qh +++ b/qcsrc/server/mutators/events.qh @@@ -10,6 -10,7 +10,7 @@@ /** called when a player becomes observer, after shared setup */ #define EV_MakePlayerObserver(i, o) \ /** player */ i(entity, MUTATOR_ARGV_0_entity) \ + /** is_forced */ i(bool, MUTATOR_ARGV_1_bool) \ /**/ MUTATOR_HOOKABLE(MakePlayerObserver, EV_MakePlayerObserver) @@@ -316,8 -317,17 +317,17 @@@ MUTATOR_HOOKABLE(PlayerPreThink, EV_Pla /**/ MUTATOR_HOOKABLE(GetPressedKeys, EV_GetPressedKeys); - /** is meant to call GetCvars_handle*(get_cvars_s, get_cvars_f, cvarfield, "cvarname") for cvars this mutator needs from the client */ - // NOTE: requesting cvar values (get_cvars_f 0) is deprecated + /** + * is meant to call GetCvars_handle* for cvars this mutator needs from the client, e.g.: + MUTATOR_HOOKFUNCTION(mymutator, GetCvars) + { + GetCvars_handleFloat(this, store, s, f, cvar_mycvar, "mycvar"); + return false; + } + * Usually you can just use REPLICATE instead of this hook, e.g.: + REPLICATE(cvar_mycvar, int, "mycvar"); + * NOTE: requesting cvar values (get_cvars_f 0) is deprecated + */ #define EV_GetCvars(i, o) \ /**/ i(float, get_cvars_f) \ /**/ i(string, get_cvars_s) \ @@@ -704,24 -714,27 +714,27 @@@ enum /**/ MUTATOR_HOOKABLE(ItemTouched, EV_ItemTouched); + // The Resource hooks are often called by other hooks and to avoid conflicts + // as much as possible their args start from ARGV_7 + /** Called when the amount of entity resources changes. Can be used to override resource limit. */ #define EV_GetResourceLimit(i, o) \ - /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /** limit */ i(float, MUTATOR_ARGV_2_float) \ - /**/ o(float, MUTATOR_ARGV_2_float) \ + /** checked entity */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /** limit */ i(float, MUTATOR_ARGV_9_float) \ + /**/ o(float, MUTATOR_ARGV_9_float) \ /**/ MUTATOR_HOOKABLE(GetResourceLimit, EV_GetResourceLimit); /** Called when the amount of resource of an entity changes. See RES_* constants for resource types. Return true to forbid the change. */ #define EV_SetResource(i, o) \ - /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /**/ o(int, MUTATOR_ARGV_1_int) \ - /** amount */ i(float, MUTATOR_ARGV_2_float) \ - /**/ o(float, MUTATOR_ARGV_2_float) \ + /** checked entity */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /**/ o(entity, MUTATOR_ARGV_8_entity) \ + /** amount */ i(float, MUTATOR_ARGV_9_float) \ + /**/ o(float, MUTATOR_ARGV_9_float) \ /**/ MUTATOR_HOOKABLE(SetResource, EV_SetResource); @@@ -729,9 -742,9 +742,9 @@@ constants for resource types. Amount wasted is the amount of resource that is above resource limit so it was not given. */ #define EV_ResourceAmountChanged(i, o) \ - /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /** amount */ i(float, MUTATOR_ARGV_2_float) \ + /** checked entity */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /** amount */ i(float, MUTATOR_ARGV_9_float) \ /**/ MUTATOR_HOOKABLE(ResourceAmountChanged, EV_ResourceAmountChanged); @@@ -739,9 -752,9 +752,9 @@@ limit. See RES_* constants for resource types. Amount wasted is the amount of resource that is above resource limit so it was not given. */ #define EV_ResourceWasted(i, o) \ - /** checked entity */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /** amount wasted */ i(float, MUTATOR_ARGV_2_float) \ + /** checked entity */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /** amount wasted */ i(float, MUTATOR_ARGV_9_float) \ /**/ MUTATOR_HOOKABLE(ResourceWasted, EV_ResourceWasted); @@@ -749,24 -762,24 +762,24 @@@ for resource types. Return true to forbid giving. NOTE: This hook is also called by GiveResourceWithLimit */ #define EV_GiveResource(i, o) \ - /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /**/ o(int, MUTATOR_ARGV_1_int) \ - /** amount */ i(float, MUTATOR_ARGV_2_float) \ - /**/ o(float, MUTATOR_ARGV_2_float) \ + /** receiver */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /**/ o(entity, MUTATOR_ARGV_8_entity) \ + /** amount */ i(float, MUTATOR_ARGV_9_float) \ + /**/ o(float, MUTATOR_ARGV_9_float) \ /**/ MUTATOR_HOOKABLE(GiveResource, EV_GiveResource); /** Called when entity is being given some resource with specified limit. See RES_* constants for resource types. Return true to forbid giving. */ #define EV_GiveResourceWithLimit(i, o) \ - /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /**/ o(int, MUTATOR_ARGV_1_int) \ - /** amount */ i(float, MUTATOR_ARGV_2_float) \ - /**/ o(float, MUTATOR_ARGV_2_float) \ - /** limit */ i(float, MUTATOR_ARGV_3_float) \ - /**/ o(float, MUTATOR_ARGV_3_float) \ + /** receiver */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /**/ o(entity, MUTATOR_ARGV_8_entity) \ + /** amount */ i(float, MUTATOR_ARGV_9_float) \ + /**/ o(float, MUTATOR_ARGV_9_float) \ + /** limit */ i(float, MUTATOR_ARGV_10_float) \ + /**/ o(float, MUTATOR_ARGV_10_float) \ /**/ MUTATOR_HOOKABLE(GiveResourceWithLimit, EV_GiveResourceWithLimit); @@@ -774,27 -787,29 +787,29 @@@ for resource types. Return true to forbid giving. NOTE: This hook is also called by TakeResourceWithLimit */ #define EV_TakeResource(i, o) \ - /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /**/ o(int, MUTATOR_ARGV_1_int) \ - /** amount */ i(float, MUTATOR_ARGV_2_float) \ - /**/ o(float, MUTATOR_ARGV_2_float) \ + /** receiver */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /**/ o(entity, MUTATOR_ARGV_8_entity) \ + /** amount */ i(float, MUTATOR_ARGV_9_float) \ + /**/ o(float, MUTATOR_ARGV_9_float) \ /**/ MUTATOR_HOOKABLE(TakeResource, EV_TakeResource); /** Called when some resource is being taken from an entity, with a limit. See RES_* constants for resource types. Return true to forbid giving. */ #define EV_TakeResourceWithLimit(i, o) \ - /** receiver */ i(entity, MUTATOR_ARGV_0_entity) \ - /** resource type */ i(int, MUTATOR_ARGV_1_int) \ - /**/ o(int, MUTATOR_ARGV_1_int) \ - /** amount */ i(float, MUTATOR_ARGV_2_float) \ - /**/ o(float, MUTATOR_ARGV_2_float) \ - /** limit */ i(float, MUTATOR_ARGV_3_float) \ - /**/ o(float, MUTATOR_ARGV_3_float) \ + /** receiver */ i(entity, MUTATOR_ARGV_7_entity) \ + /** resource type */ i(entity, MUTATOR_ARGV_8_entity) \ + /**/ o(entity, MUTATOR_ARGV_8_entity) \ + /** amount */ i(float, MUTATOR_ARGV_9_float) \ + /**/ o(float, MUTATOR_ARGV_9_float) \ + /** limit */ i(float, MUTATOR_ARGV_10_float) \ + /**/ o(float, MUTATOR_ARGV_10_float) \ /**/ MUTATOR_HOOKABLE(TakeResourceWithLimit, EV_TakeResourceWithLimit); + // END Resource hooks + /** called at when a player connect */ #define EV_ClientConnect(i, o) \ /** player */ i(entity, MUTATOR_ARGV_0_entity) \ @@@ -996,11 -1011,11 +1011,6 @@@ MUTATOR_HOOKABLE(AddPlayerScore, EV_Add /**/ MUTATOR_HOOKABLE(AddedPlayerScore, EV_AddPlayerScore); --#define EV_GetPlayerStatus(i, o) \ -- /** player */ i(entity, MUTATOR_ARGV_0_entity) \ -- /**/ --MUTATOR_HOOKABLE(GetPlayerStatus, EV_GetPlayerStatus); -- #define EV_SetWeaponArena(i, o) \ /** arena */ i(string, MUTATOR_ARGV_0_string) \ /**/ o(string, MUTATOR_ARGV_0_string) \ @@@ -1138,7 -1153,10 +1148,10 @@@ MUTATOR_HOOKABLE(ChatMessage, EV_ChatMe /**/ MUTATOR_HOOKABLE(ChatMessageTo, EV_ChatMessageTo); - /** return true to just restart the match, for modes that don't support readyrestart */ + /** + * return true to restart the server instead of restarting the match, for modes that don't support readyrestart. + * NOTE: ReadyRestart support is mandatory in campaign + */ MUTATOR_HOOKABLE(ReadyRestart_Deny, EV_NO_ARGS); /** called when a fusion reactor is validating its target */