Merge branch 'master' into fruitiex/gamemode_freezetag
authorFruitieX <fruitiex@gmail.com>
Wed, 17 Nov 2010 18:54:52 +0000 (20:54 +0200)
committerFruitieX <fruitiex@gmail.com>
Wed, 17 Nov 2010 18:54:52 +0000 (20:54 +0200)
20 files changed:
defaultXonotic.cfg
qcsrc/client/View.qc
qcsrc/client/hud.qc
qcsrc/common/constants.qh
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/common/util.qc
qcsrc/menu/xonotic/dialog_multiplayer_create.c
qcsrc/server/arena.qc
qcsrc/server/bot/aim.qc
qcsrc/server/cl_physics.qc
qcsrc/server/cl_player.qc
qcsrc/server/cl_weapons.qc
qcsrc/server/defs.qh
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/mutators/gamemode_freezetag.qc [new file with mode: 0644]
qcsrc/server/mutators/mutators.qh
qcsrc/server/progs.src
qcsrc/server/teamplay.qc

index 6d8aca9..21282a0 100644 (file)
@@ -179,6 +179,7 @@ seta crosshair_fireball_color "0.2 1.0 0.2" "crosshair color to display when wie
 seta crosshair_fireball_alpha 1        "crosshair alpha value to display when wielding the fireball"
 seta crosshair_fireball_size 1 "crosshair size when wielding the fireball"
 seta crosshair_ring_size 2     "bullet counter ring size for Rifle, velocity ring for Nex"
+seta crosshair_ring_alpha 0.2  "ring alpha"
 seta crosshair_campingrifle_bulletcounter_alpha 0.15
 
 seta crosshair_nexvelocity_alpha 0.15
@@ -565,6 +566,14 @@ seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo
 seta g_ctf_win_mode 0  "0: captures only, 1: captures, then points, 2: points only"
 seta g_ctf_ignore_frags 0      "1: regular frags give no points"
 
+set g_freezetag 0 "Freeze Tag: Freeze the opposing team(s) to win, unfreeze teammates by standing next to them"
+seta g_freezetag_warmup 5 "Time players get to run around before the round starts"
+seta g_freezetag_point_limit -1        "Freeze Tag point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_freezetag_point_leadlimit -1    "Freeze Tag point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_freezetag_revive_time 3 "Time it takes to revive a frozen teammate"
+seta g_freezetag_revive_extra_size 100 "Distance in qu that you can stand from a frozen teammate to keep reviving him"
+seta g_freezetag_frozen_force 0.6 "How much to multiply the force on a frozen player with"
+
 // 50% of the spawns shall be far away from any players
 set g_spawn_furthest 0.5
 // respawn delay
@@ -604,6 +613,8 @@ set g_cts_respawn_waves 0
 set g_cts_respawn_delay 0.25
 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_freezetag_respawn_waves 0
+set g_freezetag_respawn_delay 0.25
 
 // overtime
 seta timelimit_overtime 2 "duration in minutes of one added overtime, added to the timelimit"
@@ -1368,6 +1379,8 @@ seta hud_panel_engineinfo_framecounter_exponentialmovingaverage_instantupdate_ch
 seta hud_showbinds 1   "the way to show the keys to press in HUD messages: 0 displays commands, 1 bound keys, 2 both"
 seta hud_showbinds_limit 2     "maximum number of bound keys to show for a command. 0 for unlimited"
 
+seta hud_colorflash_alpha 0.5 "starting alpha of the color flash"
+
 // scoreboard
 seta scoreboard_columns default
 seta scoreboard_border_thickness 1 "scoreboard border thickness"
index 119941a..12ed722 100644 (file)
@@ -695,6 +695,17 @@ void CSQC_UpdateView(float w, float h)
         CSQC_RAPTOR_HUD();
        else
        {
+               if(gametype == GAME_FREEZETAG)
+               {
+                       if(getstati(STAT_FROZEN))
+                               drawfill('0 0 0', eX * vid_conwidth + eY * vid_conheight, '0.25 0.90 1', cvar_or("hud_colorflash_alpha", 0.5), DRAWFLAG_ADDITIVE);
+                       if(getstatf(STAT_REVIVE_PROGRESS))
+                       {
+                               DrawCircleClippedPic(eX * 0.5 * vid_conwidth + eY * 0.6 * vid_conheight, 0.1 * vid_conheight, "gfx/crosshair_ring.tga", getstatf(STAT_REVIVE_PROGRESS), '0.25 0.90 1', cvar("hud_colorflash_alpha"), DRAWFLAG_ADDITIVE);
+                               drawstring_aspect(eY * 0.64 * vid_conheight, "Revival progress", eX * vid_conwidth + eY * 0.025 * vid_conheight, '1 1 1', 1, DRAWFLAG_NORMAL);
+                       }
+               }
+
                if(cvar("r_letterbox") == 0)
                        if(cvar("viewsize") < 120)
                                CSQC_common_hud();
index f77f1c1..93d4359 100644 (file)
@@ -4515,7 +4515,7 @@ void HUD_ModIcons(void)
        if(!autocvar_hud_panel_modicons && !autocvar__hud_configure)
                return;
 
-       if (gametype != GAME_KEYHUNT && gametype != GAME_CTF && gametype != GAME_NEXBALL && gametype != GAME_CTS && gametype != GAME_RACE && gametype != GAME_CA && !autocvar__hud_configure)
+       if (gametype != GAME_KEYHUNT && gametype != GAME_CTF && gametype != GAME_NEXBALL && gametype != GAME_CTS && gametype != GAME_RACE && gametype != GAME_CA && gametype != GAME_FREEZETAG && !autocvar__hud_configure)
                return;
 
        active_panel = HUD_PANEL_MODICONS;
@@ -4552,7 +4552,7 @@ void HUD_ModIcons(void)
                HUD_Mod_NexBall(pos, mySize);
        else if(gametype == GAME_CTS || gametype == GAME_RACE)
                HUD_Mod_Race(pos, mySize);
-       else if(gametype == GAME_CA)
+       else if(gametype == GAME_CA || gametype == GAME_FREEZETAG)
                HUD_Mod_CA(pos, mySize);
 }
 
index 4acf797..442f70c 100644 (file)
@@ -39,6 +39,7 @@ const float GAME_RACE = 11;
 const float GAME_NEXBALL = 12;
 const float GAME_CTS = 13;
 const float GAME_CA            = 14;
+const float GAME_FREEZETAG             = 15;
 
 const float AS_STRING          = 1;
 const float AS_INT             = 2;
@@ -341,6 +342,12 @@ const float STAT_VEHICLESTAT_RELOAD2 = 66;
 // mod stats (1xx)
 const float STAT_REDALIVE = 100;
 const float STAT_BLUEALIVE = 101;
+const float STAT_YELLOWALIVE = 102;
+const float STAT_PINKALIVE = 103;
+
+// freeze tag
+const float STAT_FROZEN = 104;
+const float STAT_REVIVE_PROGRESS = 105;
 
 //const float STAT_SPIDERBOT_AIM     53 // compressShotOrigin
 //const float STAT_SPIDERBOT_TARGET  54 // compressShotOrigin
index 5192b75..895639e 100644 (file)
@@ -350,6 +350,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
 
                if(spawnpoints >= 8  && diameter > 4096) {
                        MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
+                       MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_FREEZETAG;
                        MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_CA;
                }
                if(                     diameter < 4096)
@@ -406,6 +407,7 @@ string _MapInfo_GetDefault(float t)
                case MAPINFO_TYPE_ONSLAUGHT:       return "20 0";
                case MAPINFO_TYPE_NEXBALL:         return "5 20 0";
                case MAPINFO_TYPE_CTS:             return "20 0 0";
+               case MAPINFO_TYPE_FREEZETAG:       return "10 20 0";
                default:                           return "";
        }
 }
@@ -526,6 +528,7 @@ string _MapInfo_GetDefaultEx(float t)
                case MAPINFO_TYPE_ONSLAUGHT:       return "timelimit=20";
                case MAPINFO_TYPE_NEXBALL:         return "timelimit=20 pointlimit=5 leadlimit=0";
                case MAPINFO_TYPE_CTS:             return "timelimit=20 skill=-1";
+               case MAPINFO_TYPE_FREEZETAG:       return "timelimit=20 pointlimit=10 teams=2 leadlimit=0";
                default:                           return "";
        }
 }
@@ -650,6 +653,7 @@ float MapInfo_Type_FromString(string t)
        else if(t == "rc")      return MAPINFO_TYPE_RACE;
        else if(t == "nexball") return MAPINFO_TYPE_NEXBALL;
        else if(t == "cts")     return MAPINFO_TYPE_CTS;
+       else if(t == "freezetag")       return MAPINFO_TYPE_FREEZETAG;
        else if(t == "all")     return MAPINFO_TYPE_ALL;
        else                    return 0;
 }
@@ -670,6 +674,7 @@ string MapInfo_Type_ToString(float t)
        else if(t == MAPINFO_TYPE_RACE)            return "rc";
        else if(t == MAPINFO_TYPE_NEXBALL)         return "nexball";
        else if(t == MAPINFO_TYPE_CTS)             return "cts";
+       else if(t == MAPINFO_TYPE_FREEZETAG)       return "freezetag";
        else if(t == MAPINFO_TYPE_ALL)             return "all";
        else                                       return "";
 }
@@ -1134,6 +1139,8 @@ float MapInfo_CurrentGametype()
                return MAPINFO_TYPE_NEXBALL;
        else if(cvar("g_cts"))
                return MAPINFO_TYPE_CTS;
+       else if(cvar("g_freezetag"))
+               return MAPINFO_TYPE_FREEZETAG;
        else
                return MAPINFO_TYPE_DEATHMATCH;
 }
@@ -1174,6 +1181,7 @@ string MapInfo_GetGameTypeCvar(float t)
                case MAPINFO_TYPE_ONSLAUGHT: return "g_onslaught";
                case MAPINFO_TYPE_RACE: return "g_race";
                case MAPINFO_TYPE_NEXBALL: return "g_nexball";
+               case MAPINFO_TYPE_FREEZETAG: return "g_freezetag";
                case MAPINFO_TYPE_CTS: return "g_cts";
                default: return "";
        }
@@ -1196,6 +1204,7 @@ void MapInfo_SwitchGameType(float t)
        cvar_set("g_race",       (t == MAPINFO_TYPE_RACE)            ? "1" : "0");
        cvar_set("g_nexball",    (t == MAPINFO_TYPE_NEXBALL)         ? "1" : "0");
        cvar_set("g_cts",        (t == MAPINFO_TYPE_CTS)             ? "1" : "0");
+       cvar_set("g_freezetag",  (t == MAPINFO_TYPE_FREEZETAG)       ? "1" : "0");
 }
 
 void MapInfo_LoadMap(string s)
index 1fcc0dc..641de1b 100644 (file)
@@ -12,7 +12,8 @@ float MAPINFO_TYPE_KEYHUNT            = 1024;
 float MAPINFO_TYPE_ASSAULT             = 2048;
 float MAPINFO_TYPE_ONSLAUGHT           = 4096;
 float MAPINFO_TYPE_NEXBALL             = 8192;
-float MAPINFO_TYPE_ALL                 = 16383; // this has to include all above bits
+float MAPINFO_TYPE_FREEZETAG           = 16384;
+float MAPINFO_TYPE_ALL                 = 32767; // this has to include all above bits
 
 float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
 
index 8c87b7f..85f2e0c 100644 (file)
@@ -439,6 +439,7 @@ string GametypeNameFromType(float g)
        else if (g == GAME_RACE) return "rc";
        else if (g == GAME_NEXBALL) return "nexball";
        else if (g == GAME_CTS) return "cts";
+       else if (g == GAME_FREEZETAG) return "freezetag";
        return "dm";
 }
 
index 4fb8ac0..792be64 100644 (file)
@@ -44,13 +44,15 @@ void XonoticServerCreateTab_fill(entity me)
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_cts", "Race CTS"));
                        if(e.checked) e0 = NULL;
        me.TR(me);
-               n = 8;
+               n = 9;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_tdm", "TDM"));
                        if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_ctf", "CTF"));
                        if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_ca", "CA"));
                        if(e.checked) e0 = NULL;
+               me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_freezetag", "Freeze Tag"));
+                       if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_domination", "Domination"));
                        if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_keyhunt", "Key Hunt"));
index 0467431..543a69d 100644 (file)
@@ -24,6 +24,12 @@ void func_breakable_reset();
 void assault_objective_reset();
 void target_assault_roundend_reset();
 
+float next_round;
+float stopalivecheck;
+float redalive, bluealive, yellowalive, pinkalive;
+.float redalive_stat, bluealive_stat, yellowalive_stat, pinkalive_stat;
+float redspawned, bluespawned, yellowspawned, pinkspawned;
+
 /**
  * Resets the state of all clients, items, flags, runes, keys, weapons, waypoints, ... of the map.
  * Sets the 'warmup' global variable.
@@ -39,6 +45,10 @@ void reset_map(float dorespawn)
                warmup = time + cvar("g_ca_warmup");
                allowed_to_spawn = 1;
        }
+       else if(g_freezetag)
+       {
+               warmup = time + cvar("g_freezetag_warmup");
+       }
 
        lms_lowest_lives = 999;
        lms_next_place = player_count;
@@ -92,6 +102,11 @@ void reset_map(float dorespawn)
                                self.classname = "player";
                                PutClientInServer();
                        }
+                       else if(g_freezetag)
+                       {
+                               if(self.classname == "player")
+                                       PutClientInServer();
+                       }
                        else
                        {
                                /*
@@ -192,8 +207,20 @@ void Arena_Warmup()
        float f;
        string msg;
 
-       if((!g_arena && !g_ca) || (g_arena && !arena_roundbased) || (time < game_starttime))
+       if((!g_arena && !g_ca && !g_freezetag) || (g_arena && !arena_roundbased) || (time < game_starttime))
+               return;
+
+       if(g_freezetag &&
+               !((redspawned >= 1 && bluespawned >= 1)
+               || (redspawned >= 1 && yellowspawned >= 1)
+               || (redspawned >= 1 && pinkspawned >= 1)
+               || (bluespawned >= 1 && yellowspawned >= 1)
+               || (bluespawned >= 1 && pinkspawned >= 1)
+               || (yellowspawned >= 1 && pinkspawned >= 1)))
+       { // no teams, or only one team has players
+               warmup = time + cvar("g_freezetag_warmup");
                return;
+       }
 
        f = ceil(warmup - time);
        if(f > 0)
@@ -256,50 +283,76 @@ void Arena_Warmup()
                }
        }
 
-       if(self.classname == "player" && self.health > 0)
+       if(self.classname == "player" && self.health > 0 && self.movetype == MOVETYPE_NONE)
                self.movetype = MOVETYPE_WALK;
 }
 
-float next_round;
-float stopalivecheck;
-float redalive, bluealive;
-.float redalive_stat, bluealive_stat;
-/**
- * This function finds out whether an arena round is over 1 player is left.
- * It determines the last player who's still alive and saves it's entity reference
- * in the global variable 'champion'. Then the new enemy/enemies are put into the server.
- *
- * Gets called in StartFrame()
- */
-void Spawnqueue_Check()
+void count_spawned_players()
 {
-       if(g_ca) // we want to perform this before the return block below...
+       // TODO fix "*spawned" name, it should rather be "*players" or so
+       // not doing this not to prevent merge hell with Tag
+
+       // count amount of players in each team
+       redspawned = bluespawned = yellowspawned = pinkspawned = 0;
+       FOR_EACH_PLAYER(self) {
+               if (self.team == COLOR_TEAM1) redspawned += 1;
+               else if (self.team == COLOR_TEAM2) bluespawned += 1;
+               else if (self.team == COLOR_TEAM3) yellowspawned += 1;
+               else if (self.team == COLOR_TEAM4) pinkspawned += 1;
+       }
+}
+
+void count_alive_players()
+{
+       redalive = bluealive = yellowalive = pinkalive = 0;
+       if(g_ca)
        {
-               // this is STUPID to perform again, but has to be done so that we can give instant feedback when a round ends
-               // and so the code won't start searching for a champion using find() before all players are actually REMOVED
-               redalive = 0; bluealive = 0;
                FOR_EACH_PLAYER(self) {
                        if (self.team == COLOR_TEAM1 && self.health >= 1) redalive += 1;
                        else if (self.team == COLOR_TEAM2 && self.health >= 1) bluealive += 1;
                }
-               // as if the above stuff wasn't stupid enough, let's run it a third time! :D
-               // (so that we can send redalive/bluealive as a stat)
                FOR_EACH_PLAYER(self) {
                        self.redalive_stat = redalive;
                        self.bluealive_stat = bluealive;
                }
        }
+       else if(g_freezetag)
+       {
+               // count amount of alive players in each team
+               FOR_EACH_PLAYER(self) {
+                       if (self.team == COLOR_TEAM1 && self.freezetag_frozen == 0 && self.health >= 1) redalive += 1;
+                       else if (self.team == COLOR_TEAM2 && self.freezetag_frozen == 0 && self.health >= 1) bluealive += 1;
+                       else if (self.team == COLOR_TEAM3 && self.freezetag_frozen == 0 && self.health >= 1) yellowalive += 1;
+                       else if (self.team == COLOR_TEAM4 && self.freezetag_frozen == 0 && self.health >= 1) pinkalive += 1;
+               }
+               FOR_EACH_PLAYER(self) {
+                       self.redalive_stat = redalive;
+                       self.bluealive_stat = bluealive;
+                       self.yellowalive_stat = yellowalive;
+                       self.pinkalive_stat = pinkalive;
+               }
+       }
+
+}
+
+/**
+ * This function finds out whether an arena round is over 1 player is left.
+ * It determines the last player who's still alive and saves it's entity reference
+ * in the global variable 'champion'. Then the new enemy/enemies are put into the server.
+ *
+ * Gets called in StartFrame()
+ */
+void Spawnqueue_Check()
+{
+       count_spawned_players();
+       if(g_ca || g_freezetag) // we want to perform this before the return block below (CA)...
+       {
+               count_alive_players();
+       }
        if(time < warmup + 1 || inWarmupStage)
                return;
 
        if(g_ca) {
-               // check the amount of spawned players in each team
-               float redspawned, bluespawned;
-               FOR_EACH_PLAYER(self) {
-                       if (self.team == COLOR_TEAM1) redspawned += 1;
-                       else if (self.team == COLOR_TEAM2) bluespawned += 1;
-               }
-
                required_ca_players = max(2, fabs(cvar("bot_vs_human") + 1));
 
                if(ca_players < required_ca_players && (redspawned && bluespawned)) {
@@ -348,6 +401,12 @@ void Spawnqueue_Check()
                        next_round = 0;
                        reset_map(TRUE);
                }
+       } else if(g_freezetag) {
+               if((next_round && next_round < time))
+               {
+                       next_round = 0;
+                       reset_map(TRUE);
+               }
        } else { // arena
                //extend next_round if it isn't set yet and only 1 player is spawned
                if(!next_round)
index d1c9a5a..e6950dd 100644 (file)
@@ -109,6 +109,10 @@ float bot_shouldattack(entity e)
                        return FALSE;
        }
 
+       if(g_freezetag)
+               if(e.freezetag_frozen)
+                       return FALSE;
+
        if(teams_matter)
        {
                if(e.team==0)
index efc9118..3455fde 100644 (file)
@@ -46,6 +46,9 @@ When you press the jump key
 */
 void PlayerJump (void)
 {
+       if(g_freezetag && self.freezetag_frozen)
+               return; // no jumping in freezetag when frozen
+
        float mjumpheight;
        float doublejump;
 
index a2943a7..9d5f724 100644 (file)
@@ -386,6 +386,7 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float
 }
 
 void ClientKill_Now_TeamChange();
+void freezetag_CheckWinner();
 
 void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
@@ -595,22 +596,37 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht
                        }
                }
 
-               // become fully visible
-               self.alpha = 1;
-               // clear selected player display
-               ClearSelectedPlayer();
-               // throw a weapon
-               SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon);
+               if(!g_freezetag)
+               {
+                       // become fully visible
+                       self.alpha = 1;
+                       // clear selected player display
+                       ClearSelectedPlayer();
+                       // throw a weapon
+                       SpawnThrownWeapon (self.origin + (self.mins + self.maxs) * 0.5, self.switchweapon);
+               }
+
                // print an obituary message
                Obituary (attacker, inflictor, self, deathtype);
                race_PreDie();
                DropAllRunes(self);
 
+               if(deathtype == DEATH_HURTTRIGGER)
+               {
+                       PutClientInServer();
+                       count_alive_players(); // re-count players
+                       freezetag_CheckWinner();
+                       return;
+               }
+
                frag_attacker = attacker;
                frag_inflictor = inflictor;
                frag_target = self;
                MUTATOR_CALLHOOK(PlayerDies);
 
+               if(g_freezetag)
+                       return;
+
                if(self.flagcarried)
                {
                        if(attacker.classname != "player" && attacker.classname != "gib")
index 477b641..5b76539 100644 (file)
@@ -310,7 +310,10 @@ void W_WeaponFrame()
        if (frametime)
                self.weapon_frametime = frametime;
 
-       if(((arena_roundbased || g_ca) && time < warmup) || ((time < game_starttime) && !cvar("sv_ready_restart_after_countdown")))
+       if(((arena_roundbased || g_ca || g_freezetag) && time < warmup) || ((time < game_starttime) && !cvar("sv_ready_restart_after_countdown")))
+               return;
+
+       if(g_freezetag && self.freezetag_frozen == 1)
                return;
 
        if (!self.weaponentity || self.health < 1)
index d369298..c5a20b3 100644 (file)
@@ -17,7 +17,7 @@ float require_spawnfunc_prefix; // if this float exists, only functions with spa
 
 float ctf_score_value(string parameter);
 
-float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts;
+float g_dm, g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_ca, g_lms, g_runematch, g_race, g_nexball, g_cts, g_freezetag;
 float g_cloaked, g_footsteps, g_jump_grunt, g_grappling_hook, g_midair, g_minstagib, g_pinata, g_norecoil, g_minstagib_invis_alpha, g_bloodloss;
 float g_warmup_limit;
 float g_warmup_allguns;
@@ -661,3 +661,7 @@ float allowed_to_spawn; // boolean variable used by the clan arena code to deter
 float serverflags;
 
 .float team_forced; // can be a team number to force a team, or 0 for default action, or -1 for forced spectator
+
+.float freezetag_frozen;
+.float freezetag_beginrevive_time;
+.float freezetag_revive_progress;
index 460c608..1a4ce71 100644 (file)
@@ -526,6 +526,15 @@ void Damage (entity targ, entity inflictor, entity attacker, float damage, float
                if (attacker.isbot)
                        damage = damage * bound(0.1, (skill + 5) * 0.1, 1);
 
+               if(g_freezetag)
+               {
+                       if(targ.freezetag_frozen == 1)
+                       {
+                               damage = 0;
+                               force = force * cvar("g_freezetag_frozen_force");
+                       }
+               }
+
                // nullify damage if teamplay is on
                if(deathtype != DEATH_TELEFRAG)
                if(attacker.classname == "player")
index f381612..9d6524a 100644 (file)
@@ -809,11 +809,19 @@ void spawnfunc_worldspawn (void)
 
        addstat(STAT_NEX_CHARGE, AS_FLOAT, nex_charge);
 
-       if(g_ca)
+       if(g_ca || g_freezetag)
        {
                addstat(STAT_REDALIVE, AS_INT, redalive_stat);
                addstat(STAT_BLUEALIVE, AS_INT, bluealive_stat);
+               addstat(STAT_YELLOWALIVE, AS_INT, yellowalive_stat);
+               addstat(STAT_PINKALIVE, AS_INT, pinkalive_stat);
        }
+       if(g_freezetag)
+       {
+               addstat(STAT_FROZEN, AS_INT, freezetag_frozen);
+               addstat(STAT_REVIVE_PROGRESS, AS_FLOAT, freezetag_revive_progress);
+       }
+
        // g_movementspeed hack
        addstat(STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW, AS_FLOAT, stat_sv_airspeedlimit_nonqw);
        addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw);
diff --git a/qcsrc/server/mutators/gamemode_freezetag.qc b/qcsrc/server/mutators/gamemode_freezetag.qc
new file mode 100644 (file)
index 0000000..3e5deb2
--- /dev/null
@@ -0,0 +1,245 @@
+void freezetag_Initialize()
+{
+       precache_model("models/ice/ice.md3");
+       warmup = time + cvar("g_freezetag_warmup");
+}
+
+void freezetag_CheckWinner()
+{
+       if(next_round || (time > warmup - cvar("g_freezetag_warmup") && time < warmup))
+               return; // already waiting for next round to start
+
+       if((redalive >= 1 && bluealive >= 1) // counted in arena.qc
+               || (redalive >= 1 && yellowalive >= 1)
+               || (redalive >= 1 && pinkalive >= 1)
+               || (bluealive >= 1 && yellowalive >= 1)
+               || (bluealive >= 1 && pinkalive >= 1)
+               || (yellowalive >= 1 && pinkalive >= 1))
+               return; // we still have active players on two or more teams
+
+       if(redalive + bluealive + yellowalive + pinkalive <= 0)
+       {
+               next_round = time + 5;
+               return;
+       }
+
+       entity e, winner;
+       string teamname;
+
+       FOR_EACH_PLAYER(e)
+       {
+               if(e.freezetag_frozen == 0 && e.classname == "player" && e.health >= 1) // here's one player from the winning team... good
+               {
+                       winner = e;
+                       TeamScore_AddToTeam(winner.team, ST_SCORE, +1); // just in case a winner isn't found, we do this already here (causes crashes otherwise...)
+                       break; // break, we found the winner
+               }
+       }
+
+       if(winner.team == COLOR_TEAM1)
+               teamname = "^1Red Team";
+       else if(winner.team == COLOR_TEAM2)
+               teamname = "^4Blue Team";
+       else if(winner.team == COLOR_TEAM3)
+               teamname = "^3Yellow Team";
+       else
+               teamname = "^6Pink Team";
+       FOR_EACH_PLAYER(e) {
+               centerprint(e, strcat(teamname, "^5 wins the round, all other teams were frozen.\n"));
+       }
+       bprint(teamname, "^5 wins the round since all the other teams were frozen.\n");
+
+       next_round = time + 5;
+}
+
+void freezetag_Ice_Think()
+{
+       setorigin(self, self.owner.origin - '0 0 16');
+       self.nextthink = time;
+}
+
+void freezetag_Freeze()
+{
+       self.freezetag_frozen = 1;
+
+       entity ice;
+       ice = spawn();
+       ice.owner = self;
+       ice.classname = "freezetag_ice";
+       ice.think = freezetag_Ice_Think;
+       ice.nextthink = time;
+       ice.frame = floor(random() * 21); // ice model has 20 different looking frames
+       setmodel(ice, "models/ice/ice.md3");
+
+       self.movement = '0 0 0';
+
+       // add waypoint
+       WaypointSprite_Spawn("freezetag_frozen", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attached, TRUE);
+       if(self.waypointsprite_attached)
+       {
+               WaypointSprite_UpdateTeamRadar(self.waypointsprite_attached, RADARICON_WAYPOINT, '0.25 0.90 1');
+       }
+}
+
+void freezetag_Unfreeze()
+{
+       self.freezetag_frozen = 0;
+
+       // remove the ice block
+       entity ice;
+       for(ice = world; (ice = find(ice, classname, "freezetag_ice")); ) if(ice.owner == self)
+       {
+               remove(ice);
+               break;
+       }
+
+       // remove waypoint
+       if(self.waypointsprite_attached)
+               WaypointSprite_Kill(self.waypointsprite_attached);
+}
+
+MUTATOR_HOOKFUNCTION(freezetag_RemovePlayer)
+{
+       freezetag_CheckWinner();
+       freezetag_Unfreeze();
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(freezetag_PlayerDies)
+{
+       // if the player was previously not frozen (should be the case anyway), decrement alive playercount before running CheckWinner
+       if (self.team == COLOR_TEAM1 && self.freezetag_frozen == 0) redalive -= 1;
+       else if (self.team == COLOR_TEAM2 && self.freezetag_frozen == 0) bluealive -= 1;
+       else if (self.team == COLOR_TEAM3 && self.freezetag_frozen == 0) yellowalive -= 1;
+       else if (self.team == COLOR_TEAM4 && self.freezetag_frozen == 0) pinkalive -= 1;
+       
+       freezetag_Freeze();
+
+       centerprint(frag_attacker, strcat("^2You froze ^7", frag_target.netname, ".\n"));
+       if(frag_attacker == frag_target || frag_attacker == world)
+       {
+               centerprint(frag_target, "^1You froze yourself.\n");
+               bprint("^7", frag_target.netname, "^1 froze himself.\n");
+       }
+       else
+       {
+               centerprint(frag_target, strcat("^1You were frozen by ^7", frag_attacker.netname, ".\n"));
+               bprint("^7", frag_target.netname, "^1 was frozen by ^7", frag_attacker.netname, ".\n");
+       }
+
+       frag_target.health = cvar("g_balance_health_start"); // "respawn" the player :P
+
+       freezetag_CheckWinner();
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(freezetag_PlayerSpawn)
+{
+       if(time > warmup) // spawn too late, freeze player
+       {
+               centerprint(self, "^1You spawned after the round started, you'll spawn as frozen.\n");
+               freezetag_Freeze();
+       }
+       else // we are still in the delay period before the round starts
+       {
+               freezetag_Unfreeze();
+       }
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(freezetag_GiveFragsForKill)
+{
+       frag_score = 0; // no frags counted in Freeze Tag
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(freezetag_PlayerPreThink)
+{
+       vector revive_extra_size;
+       revive_extra_size = '1 1 1' * cvar("g_freezetag_revive_extra_size");
+
+       float teammate_nearby;
+       FOR_EACH_PLAYER(other) if(self != other)
+       {
+               if(other.freezetag_frozen == 0)
+               {
+                       if(other.team == self.team)
+                       {
+                               teammate_nearby = boxesoverlap(self.absmin - revive_extra_size, self.absmax + revive_extra_size, other.absmin, other.absmax);
+                               break;
+                       }
+               }
+       }
+
+       if(teammate_nearby && self.freezetag_frozen == 1)
+       {
+               if(self.freezetag_beginrevive_time == -9999)
+               {
+                       self.freezetag_beginrevive_time = time;
+                       self.freezetag_revive_progress = 0;
+                       other.freezetag_revive_progress = 0;
+               }
+               else
+               {
+                       self.freezetag_revive_progress = (time - self.freezetag_beginrevive_time) / cvar("g_freezetag_revive_time");
+                       other.freezetag_revive_progress = (time - self.freezetag_beginrevive_time) / cvar("g_freezetag_revive_time");
+                       if(time - self.freezetag_beginrevive_time >= cvar("g_freezetag_revive_time"))
+                       {
+                               freezetag_Unfreeze();
+
+                               centerprint(self, strcat("^5You were revived by ^7", other.netname, ".\n"));
+                               centerprint(other, strcat("^5You revived ^7", self.netname, ".\n"));
+                               bprint("^7", other.netname, "^5 revived ^7", self.netname, ".\n");
+
+                               self.freezetag_beginrevive_time = -9999;
+                               self.freezetag_revive_progress = 0;
+                               other.freezetag_revive_progress = 0;
+                       }
+               }
+       }
+       else
+       {
+               self.freezetag_beginrevive_time = -9999;
+               self.freezetag_revive_progress = 0;
+               other.freezetag_revive_progress = 0;
+       }
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(freezetag_PlayerPhysics)
+{
+       if(self.freezetag_frozen)
+               self.movement = '0 0 0';
+       return 1;
+}
+
+MUTATOR_DEFINITION(gamemode_freezetag)
+{
+       MUTATOR_HOOK(MakePlayerObserver, freezetag_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(ClientDisconnect, freezetag_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDies, freezetag_PlayerDies, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerSpawn, freezetag_PlayerSpawn, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GiveFragsForKill, freezetag_GiveFragsForKill, CBC_ORDER_FIRST);
+       MUTATOR_HOOK(PlayerPreThink, freezetag_PlayerPreThink, CBC_ORDER_FIRST);
+       MUTATOR_HOOK(PlayerPhysics, freezetag_PlayerPhysics, CBC_ORDER_FIRST);
+
+       MUTATOR_ONADD
+       {
+               if(time > 1) // game loads at time 1
+                       error("This is a game type and it cannot be added at runtime.");
+               g_freezetag = 1;
+               freezetag_Initialize();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               g_freezetag = 0;
+               error("This is a game type and it cannot be removed at runtime.");
+       }
+
+       return 0;
+}
index 542014e..a57db07 100644 (file)
@@ -1,4 +1,5 @@
 MUTATOR_DECLARATION(gamemode_keyhunt);
+MUTATOR_DECLARATION(gamemode_freezetag);
 
 MUTATOR_DECLARATION(mutator_nix);
 MUTATOR_DECLARATION(mutator_dodging);
index c2718d4..b9d7fc1 100644 (file)
@@ -177,6 +177,7 @@ cheats.qc
 
 mutators/base.qc
 mutators/gamemode_keyhunt.qc
+mutators/gamemode_freezetag.qc
 mutators/mutator_nix.qc
 mutators/mutator_dodging.qc
 mutators/mutator_rocketflying.qc
index b9064c7..003ca27 100644 (file)
@@ -101,6 +101,7 @@ void WriteGameCvars()
        cvar_set("g_race", ftos(g_race));
        cvar_set("g_nexball", ftos(g_nexball));
        cvar_set("g_cts", ftos(g_cts));
+       cvar_set("g_freezetag", ftos(g_freezetag));
 }
 
 void ReadGameCvars()
@@ -127,6 +128,7 @@ void ReadGameCvars()
                found += (g_race = (!found && (prev != GAME_RACE) && cvar("g_race")));
                found += (g_nexball = (!found && (prev != GAME_NEXBALL) && cvar("g_nexball")));
                found += (g_cts = (!found && (prev != GAME_CTS) && cvar("g_cts")));
+               found += (g_freezetag = (!found && (prev != GAME_FREEZETAG) && cvar("g_freezetag")));
 
                if(found)
                        break;
@@ -317,6 +319,16 @@ void InitGameplayMode()
                MUTATOR_ADD(gamemode_keyhunt);
        }
 
+       if(g_freezetag)
+       {
+               game = GAME_FREEZETAG;
+               gamemode_name = "Freeze Tag";
+               ActivateTeamplay();
+               fraglimit_override = cvar("g_freezetag_point_limit");
+               leadlimit_override = cvar("g_freezetag_point_leadlimit");
+               MUTATOR_ADD(gamemode_freezetag);
+       }
+
        if(g_assault)
        {
                game = GAME_ASSAULT;
@@ -816,6 +828,8 @@ float FindSmallestTeam(entity pl, float ignore_pl)
                        error("Too few teams available for ctf\n");
                else if(g_keyhunt)
                        error("Too few teams available for key hunt\n");
+               else if(g_freezetag)
+                       error("Too few teams available for freeze tag\n");
                else
                        error("Too few teams available for team deathmatch\n");
        }