]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/survival
authorMario <mario.mario@y7mail.com>
Fri, 1 Jul 2022 05:46:31 +0000 (15:46 +1000)
committerMario <mario.mario@y7mail.com>
Fri, 1 Jul 2022 05:46:31 +0000 (15:46 +1000)
1  2 
gamemodes-client.cfg
gamemodes-server.cfg
notifications.cfg
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/menu/xonotic/util.qc
qcsrc/server/mutators/events.qh

index 45d0b7a3f3e0107b51bd41bb6fa223af772ed2d9,71d272a174e7c50dcb868fa34e472d043668a250..1bc0853c690e18ddd0b5ced19f54977eb2a2531e
@@@ -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_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends
 +alias cl_hook_gamestart_sv
+ alias cl_hook_gameend
  alias cl_hook_shutdown
  alias cl_hook_activeweapon
Simple merge
Simple merge
Simple merge
index ec7c40f8de7e6b33cb53987158d022cde889c3ff,0000000000000000000000000000000000000000..aa3631af9ed992ad8b9a5861be86ff31b668e6f8
mode 100644,000000..100644
--- /dev/null
@@@ -1,459 -1,0 +1,453 @@@
-       if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
 +#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 == 0.5)
++      if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
 +              return true;
-       if(IS_PLAYER(player) || player.caplayer)
++      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);
 +
-       player.caplayer = 1;
++      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;
-       if (!allowed_to_spawn && player.caplayer)
++      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 (CS(player).jointime != time && !player.caplayer) // not when connecting
++      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);
-                       player.caplayer = 0.5;
++              if (CS(player).jointime != time && !INGAME(player)) // not when connecting
 +              {
-               if (!it.caplayer && IS_BOT_CLIENT(it))
-                       it.caplayer = 1;
-               if (it.caplayer)
++                      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
-                       it.caplayer = 1;
++              if (INGAME(it) || IS_BOT_CLIENT(it))
 +              {
 +                      TRANSMUTE(Player, it);
-               player.caplayer = 0;
-       if (player.caplayer)
++                      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
-       if (!player.caplayer)
++              INGAME_STATUS_CLEAR(player);
++      if (INGAME(player))
 +              player.frags = FRAGS_PLAYER_OUT_OF_GAME;
 +      if (!warmup_stage)
 +              eliminatedPlayers.SendFlags |= 1;
-               if (IS_PLAYER(it) || it.caplayer == 1)
++      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 (player.caplayer)
++              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);
 +
- MUTATOR_HOOKFUNCTION(sv, GetPlayerStatus)
- {
-       entity player = M_ARGV(0, entity);
-       return player.caplayer == 1;
- }
++      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, BotShouldAttack)
 +{
 +      entity bot = M_ARGV(0, entity);
 +      entity targ = M_ARGV(1, entity);
 +
 +      if(targ.survival_status == bot.survival_status)
 +              return true;
 +}
Simple merge
Simple merge
Simple merge
Simple merge
index 8781061348b9ac281ed0beb66f418247509fe801,f98a8de8876e18ea5258fb5e0b6847234fc87de3..f013263b632a3fc29bbedf4586a56faab269b2fa
@@@ -681,9 -668,7 +668,9 @@@ 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_SURVIVAL) */ \
 +      /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
        /**/
  
  // hidden gametypes come last so indexing always works correctly
index f33fc4598e0f785f04a5138b27ebcab5b835125b,e7f9f897b0fdce206ccae04312aad6bbe17092b1..87b3aa16d3bc267f3a0d5ae589d25f9a7d156363
@@@ -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) \