From: LegendaryGuard Date: Mon, 15 Feb 2021 01:13:46 +0000 (+0100) Subject: Added Survival gamemode from Mario/survival branch, Survival gamemode activated in... X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=010c4879d1ad451931def619c60fc620f4502ade;p=xonotic%2Fxonotic-data.pk3dir.git Added Survival gamemode from Mario/survival branch, Survival gamemode activated in the menu --- diff --git a/gamemodes-client.cfg b/gamemodes-client.cfg index c43b9d1d3..49867e4cd 100644 --- a/gamemodes-client.cfg +++ b/gamemodes-client.cfg @@ -32,6 +32,7 @@ alias cl_hook_gamestart_ka alias cl_hook_gamestart_ft alias cl_hook_gamestart_inv alias cl_hook_gamestart_duel +alias cl_hook_gamestart_surv //LegendGuard adds survival client hook from Mario/survival 15-02-2021 alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends alias cl_hook_shutdown alias cl_hook_activeweapon diff --git a/gamemodes-server.cfg b/gamemodes-server.cfg index c15baaf9d..310236039 100644 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@ -29,6 +29,7 @@ alias sv_hook_gamestart_ka alias sv_hook_gamestart_ft alias sv_hook_gamestart_inv alias sv_hook_gamestart_duel +alias sv_hook_gamestart_sv //LegendGuard adds survival hook from Mario/survival 15-02-2021 // 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 @@ -58,6 +59,7 @@ alias sv_vote_gametype_hook_ons alias sv_vote_gametype_hook_rc alias sv_vote_gametype_hook_tdm alias sv_vote_gametype_hook_duel +alias sv_vote_gametype_hook_sv ////LegendGuard adds survival hook from Mario/survival 15-02-2021 // 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. @@ -208,6 +210,13 @@ set g_duel_respawn_delay_large_count 0 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 ////LegendGuard adds survival cvars from Mario/survival 15-02-2021 +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 // ========= @@ -556,3 +565,15 @@ set g_duel 0 "Duel: frag the opponent more in a one versus one arena battle" //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" + +//LegendGuard adds survival cvars from Mario/survival 15-02-2021 +// ========== +// 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" \ No newline at end of file diff --git a/gfx/menu/luma/gametype_sv.tga b/gfx/menu/luma/gametype_sv.tga new file mode 100644 index 000000000..050fb7b06 Binary files /dev/null and b/gfx/menu/luma/gametype_sv.tga differ diff --git a/gfx/menu/luminos/gametype_sv.tga b/gfx/menu/luminos/gametype_sv.tga new file mode 100644 index 000000000..c5486651c Binary files /dev/null and b/gfx/menu/luminos/gametype_sv.tga differ diff --git a/gfx/menu/wickedx/gametype_sv.tga b/gfx/menu/wickedx/gametype_sv.tga new file mode 100644 index 000000000..c5486651c Binary files /dev/null and b/gfx/menu/wickedx/gametype_sv.tga differ diff --git a/gfx/menu/xaw/gametype_sv.tga b/gfx/menu/xaw/gametype_sv.tga new file mode 100644 index 000000000..debbce06f Binary files /dev/null and b/gfx/menu/xaw/gametype_sv.tga differ diff --git a/notifications.cfg b/notifications.cfg index 61fafdecb..1f6fa2c97 100644 --- a/notifications.cfg +++ b/notifications.cfg @@ -283,6 +283,10 @@ seta notification_INFO_SCORES "1" "0 = off, 1 = print to console, 2 = print to c 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)" +//LegendGuard adds survival notifications from Mario/survival 15-02-2021 +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)" @@ -532,6 +536,12 @@ seta notification_CENTER_SEQUENCE_COUNTER_FEWMORE "1" "0 = off, 1 = centerprint" 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" +//LegendGuard adds survival notification from Mario/survival 15-02-2021 +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" diff --git a/qcsrc/common/ent_cs.qc b/qcsrc/common/ent_cs.qc index 7c20ec146..0f91e23ff 100644 --- a/qcsrc/common/ent_cs.qc +++ b/qcsrc/common/ent_cs.qc @@ -166,6 +166,13 @@ ENTCS_PROP(ACTIVEWEPID, false, activewepid, activewepid, ENTCS_SET_NORMAL, { WriteByte(chan, ent.activewepid); }, { ent.activewepid = ReadByte(); }) +//LegendGuard adds ENTCS_PROP from Mario/survival 15-02-2021 +// 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; diff --git a/qcsrc/common/gamemodes/gamemode/_mod.inc b/qcsrc/common/gamemodes/gamemode/_mod.inc index a33ec87a0..cd4c86e02 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/_mod.inc @@ -15,4 +15,5 @@ #include #include #include +#include //LegendGuard adds _mod.inc from Mario/survival 15-02-2021 #include diff --git a/qcsrc/common/gamemodes/gamemode/_mod.qh b/qcsrc/common/gamemodes/gamemode/_mod.qh index ffd71d59d..4aa6fa41b 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.qh +++ b/qcsrc/common/gamemodes/gamemode/_mod.qh @@ -15,4 +15,5 @@ #include #include #include +#include //LegendGuard adds _mod.qh from Mario/survival 15-02-2021 #include diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.inc b/qcsrc/common/gamemodes/gamemode/survival/_mod.inc new file mode 100644 index 000000000..7121d392e --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/_mod.inc @@ -0,0 +1,8 @@ +// generated file; do not modify +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.qh b/qcsrc/common/gamemodes/gamemode/survival/_mod.qh new file mode 100644 index 000000000..875e4d247 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/_mod.qh @@ -0,0 +1,8 @@ +// generated file; do not modify +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc new file mode 100644 index 000000000..83893e69d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc @@ -0,0 +1,82 @@ +#include "cl_survival.qh" + +#include +#include + +void HUD_Mod_Survival(vector pos, vector mySize) +{ + mod_active = 1; // survival should always show the mod HUD + + int mystatus = entcs_receiver(player_localnum).survival_status; + string player_text = ""; + vector player_color = '1 1 1'; + //string player_icon = ""; + if(mystatus == SV_STATUS_HUNTER) + { + player_text = _("Hunter"); + player_color = '1 0 0'; + //player_icon = "player_red"; + } + else if(mystatus == SV_STATUS_PREY) + { + player_text = _("Survivor"); + player_color = '0 1 0'; + //player_icon = "player_neutral"; + } + else + { + // if the player has no valid status, don't draw anything + return; + } + + string time_text = string_null; + vector timer_color = '1 1 1'; + if(!STAT(GAME_STOPPED) && !warmup_stage && STAT(SURVIVAL_ROUNDTIMER) > 0) + { + float timeleft = max(0, STAT(SURVIVAL_ROUNDTIMER) - time); + timeleft = ceil(timeleft); + float minutesLeft = floor(timeleft / 60); + time_text = seconds_tostring(timeleft); + if(intermission_time || minutesLeft >= 5 || warmup_stage || STAT(SURVIVAL_ROUNDTIMER) == 0) + timer_color = '1 1 1'; //white + else if(minutesLeft >= 1) + timer_color = '1 1 0'; //yellow + else + timer_color = '1 0 0'; //red + } + + //drawpic_aspect_skin(pos, player_icon, vec2(0.5 * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); + if(!time_text) + drawstring_aspect(pos, player_text, vec2(mySize.x, mySize.y), player_color, panel_fg_alpha, DRAWFLAG_NORMAL); + else + { + drawstring_aspect(pos, player_text, vec2(0.5 * mySize.x, mySize.y), player_color, panel_fg_alpha, DRAWFLAG_NORMAL); + drawstring_aspect(pos + eX * (0.5 * mySize.x), time_text, vec2(0.5 * mySize.x, mySize.y), timer_color, panel_fg_alpha, DRAWFLAG_NORMAL); + } +} + +REGISTER_MUTATOR(cl_sv, true); + +MUTATOR_HOOKFUNCTION(cl_sv, ForcePlayercolors_Skip, CBC_ORDER_LAST) +{ + if(!ISGAMETYPE(SURVIVAL)) + return false; + + entity player = M_ARGV(0, entity); + entity e = entcs_receiver(player.entnum - 1); + int surv_status = ((e) ? e.survival_status : 0); + int mystatus = entcs_receiver(player_localnum).survival_status; + + int plcolor = SV_COLOR_PREY; // default to survivor + if((mystatus == SV_STATUS_HUNTER || intermission || STAT(GAME_STOPPED)) && surv_status == SV_STATUS_HUNTER) + plcolor = SV_COLOR_HUNTER; + + player.colormap = 1024 + plcolor; + return true; +} + +MUTATOR_HOOKFUNCTION(cl_sv, DrawScoreboard_Force) +{ + // show the scoreboard when the round ends, so players can see who the hunter was + return STAT(GAME_STOPPED); +} diff --git a/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qh new file mode 100644 index 000000000..057120a1f --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qh @@ -0,0 +1,3 @@ +#pragma once + +void HUD_Mod_Survival(vector pos, vector mySize); diff --git a/qcsrc/common/gamemodes/gamemode/survival/survival.qc b/qcsrc/common/gamemodes/gamemode/survival/survival.qc new file mode 100644 index 000000000..1f2d14402 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/survival.qc @@ -0,0 +1 @@ +#include "survival.qh" diff --git a/qcsrc/common/gamemodes/gamemode/survival/survival.qh b/qcsrc/common/gamemodes/gamemode/survival/survival.qh new file mode 100644 index 000000000..17a01d84c --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/survival.qh @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#ifdef CSQC +void HUD_Mod_Survival(vector pos, vector mySize); +#endif +CLASS(Survival, Gametype) + INIT(Survival) + { + this.gametype_init(this, _("Survival"),"sv","g_survival",GAMETYPE_FLAG_USEPOINTS,"","timelimit=30 pointlimit=20",_("Identify and eliminate all the hunters before all your allies are gone")); + } + METHOD(Survival, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter)) + { + return true; + } + METHOD(Survival, m_isForcedSupported, bool(Gametype this)) + { + if(!cvar("g_survival_not_lms_maps")) + { + // if this is unset, all LMS maps support Survival too + if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS.m_flags)) + return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported) + } + return false; + } +#ifdef CSQC + ATTRIB(Survival, m_modicons, void(vector pos, vector mySize), HUD_Mod_Survival); +#endif +ENDCLASS(Survival) +REGISTER_GAMETYPE(SURVIVAL, NEW(Survival)); + +#ifdef GAMEQC +// shared state signalling the player's survival status +.int survival_status; +const int SV_STATUS_PREY = 1; +const int SV_STATUS_HUNTER = 2; + +// hardcoded player colors for survival +const int SV_COLOR_PREY = 51; // green +const int SV_COLOR_HUNTER = 68; // red +#endif diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc new file mode 100644 index 000000000..1c066d8b7 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc @@ -0,0 +1,459 @@ +#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)) + return true; + if(e.caplayer == 0.5) + 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) + { + // 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; + 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) + 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 + { + player.caplayer = 0.5; + 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) + { + TRANSMUTE(Player, it); + it.caplayer = 1; + 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_clear(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); + + 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) + player.frags = FRAGS_PLAYER_OUT_OF_GAME; + if (!warmup_stage) + eliminatedPlayers.SendFlags |= 1; + if (!player.caplayer) + { + 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) + ++M_ARGV(0, int); + ++M_ARGV(1, int); + }); + return true; +} + +MUTATOR_HOOKFUNCTION(sv, ClientCommand_Spectate) +{ + entity player = M_ARGV(0, entity); + + if (player.caplayer) + { + // 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 --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh new file mode 100644 index 000000000..822cfd0eb --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +void surv_Initialize(); + +REGISTER_MUTATOR(sv, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + surv_Initialize(); + } + return false; +} + +.int survival_validkills; // store the player's valid kills to be given at the end of the match (avoid exposing their score until then) diff --git a/qcsrc/common/notifications/all.inc b/qcsrc/common/notifications/all.inc index efa3d03da..b7708b4a8 100644 --- a/qcsrc/common/notifications/all.inc +++ b/qcsrc/common/notifications/all.inc @@ -497,6 +497,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_INFO_NOTIF(SPECTATE_WARNING, N_CONSOLE, 0, 1, "f1secs", "", "", _("^F2You have to become a player within the next %s, otherwise you will be kicked, because spectating isn't allowed at this time!"), "") MSG_INFO_NOTIF(SUPERWEAPON_PICKUP, N_CONSOLE, 1, 0, "s1", "s1", "superweapons", _("^BG%s^K1 picked up a Superweapon"), "") +//LegendGuard adds MSG_INFO_NOTIF from Mario/survival 15-02-2021 + 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"), "") @@ -827,6 +830,11 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input != MSG_CENTER_NOTIF(SUPERWEAPON_BROKEN, N_ENABLE, 0, 0, "", CPID_POWERUP, "0 0", _("^F2Superweapons have broken down"), "") 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"), "") +//LegendGuard adds MSG_CENTER_NOTIF from Mario/survival 15-02-2021 + 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"), "") diff --git a/qcsrc/common/notifications/all.qh b/qcsrc/common/notifications/all.qh index d08089143..354a0166f 100644 --- a/qcsrc/common/notifications/all.qh +++ b/qcsrc/common/notifications/all.qh @@ -43,7 +43,7 @@ string Get_Notif_TypeName(MSG net_type) LOG_WARNF("Get_Notif_TypeName(%d): Improper net type!", ORDINAL(net_type)); return ""; } - +//LegendGuard adds CASE(CPID, SURVIVAL) after RACE_FINISHLAP from Mario/survival 15-02-2021 ENUMCLASS(CPID) CASE(CPID, ASSAULT_ROLE) CASE(CPID, ROUND) @@ -73,6 +73,7 @@ ENUMCLASS(CPID) CASE(CPID, OVERTIME) CASE(CPID, POWERUP) CASE(CPID, RACE_FINISHLAP) + CASE(CPID, SURVIVAL) CASE(CPID, TEAMCHANGE) CASE(CPID, TIMEOUT) CASE(CPID, TIMEIN) diff --git a/qcsrc/common/scores.qh b/qcsrc/common/scores.qh index 2869541b7..09c6995b6 100644 --- a/qcsrc/common/scores.qh +++ b/qcsrc/common/scores.qh @@ -107,6 +107,9 @@ REGISTER_SP(MEDAL_KILLSTREAK_03); REGISTER_SP(MEDAL_KILLSTREAK_05); REGISTER_SP(MEDAL_KILLSTREAK_10); REGISTER_SP(MEDAL_KILLSTREAK_15); + +REGISTER_SP(SV_SURVIVALS); //LegendGuard adds REGISTER_SP from Mario/survival 15-02-2021 +REGISTER_SP(SV_HUNTS); #endif diff --git a/qcsrc/common/stats.qh b/qcsrc/common/stats.qh index 0215c8782..0d17414d0 100644 --- a/qcsrc/common/stats.qh +++ b/qcsrc/common/stats.qh @@ -156,6 +156,8 @@ REGISTER_STAT(VEIL_ORB, float) REGISTER_STAT(VEIL_ORB_ALPHA, float) REGISTER_STAT(ARMORIZING_ORB, float) //LegendGuard registers new STAT 11-02-2021 REGISTER_STAT(ARMORIZING_ORB_ALPHA, float) +REGISTER_STAT(AMMUNITIONING_ORB, float) //LegendGuard registers new STAT 13-02-2021 +REGISTER_STAT(AMMUNITIONING_ORB_ALPHA, float) REGISTER_STAT(DARK_ORB, float) //LegendGuard registers new STAT 08-02-2021 REGISTER_STAT(DARK_ORB_ALPHA, float) @@ -454,3 +456,5 @@ REGISTER_STAT(GUNALIGN, int) #ifdef SVQC SPECTATE_COPYFIELD(_STAT(GUNALIGN)) #endif + +REGISTER_STAT(SURVIVAL_ROUNDTIMER, float) //LegendGuard adds SURVIVAL_ROUNDTIMER from Mario/survival 15-02-2021 diff --git a/qcsrc/menu/xonotic/util.qc b/qcsrc/menu/xonotic/util.qc index e77049d20..3ea53be5e 100644 --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@ -683,6 +683,8 @@ float updateCompression() GAMETYPE(MAPINFO_TYPE_ASSAULT) \ /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \ /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \ + GAMETYPE(MAPINFO_TYPE_SURVIVAL) \ + //LegendGuard adds GAMETYPE for menu from Mario/survival 15-02-2021 /**/ // hidden gametypes come last so indexing always works correctly