]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Laying down some ground work for the keepaway game mode, lots more work to do for...
authorunknown <samual@xonotic.org>
Mon, 15 Nov 2010 06:54:47 +0000 (01:54 -0500)
committerunknown <samual@xonotic.org>
Mon, 15 Nov 2010 06:54:47 +0000 (01:54 -0500)
qcsrc/common/constants.qh
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/menu/xonotic/dialog_multiplayer_create.c
qcsrc/menu/xonotic/dialog_multiplayer_create_mapinfo.c
qcsrc/server/defs.qh
qcsrc/server/g_world.qc
qcsrc/server/mutators/gamemode_keepaway.qc [new file with mode: 0644]
qcsrc/server/teamplay.qc
sound/keepaway/dropped.wav [new file with mode: 0644]
sound/keepaway/pickedup.wav [new file with mode: 0644]

index 4acf797b7dddfc7974315f23c4f5e506d2a7e500..25acf94439a3f9079d975461294018ee5ef93bc1 100644 (file)
@@ -34,11 +34,12 @@ const float GAME_LMS                        = 6;
 const float GAME_ARENA         = 7;
 const float GAME_KEYHUNT               = 8;
 const float GAME_ASSAULT               = 9;
-const float GAME_ONSLAUGHT     = 10;
-const float GAME_RACE  = 11;
-const float GAME_NEXBALL = 12;
-const float GAME_CTS = 13;
-const float GAME_CA            = 14;
+const float GAME_ONSLAUGHT             = 10;
+const float GAME_RACE                  = 11;
+const float GAME_NEXBALL               = 12;
+const float GAME_CTS                   = 13;
+const float GAME_CA                    = 14;
+const float GAME_KEEPAWAY              = 15;
 
 const float AS_STRING          = 1;
 const float AS_INT             = 2;
index 5192b75ac24ea37fd4a67cc72bc885b80dd3ea60..95301c95c027612dcd92421492a197ade6a4c72b 100644 (file)
@@ -347,6 +347,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
                MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_DEATHMATCH;      // DM always works
                MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RUNEMATCH;       // Rune always works
                MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_LMS;             // LMS always works
+               MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_KEEPAWAY;                // Keepaway always works
 
                if(spawnpoints >= 8  && diameter > 4096) {
                        MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_TEAM_DEATHMATCH;
@@ -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_KEEPAWAY:        return "30 20 0";
                default:                           return "";
        }
 }
@@ -462,6 +464,14 @@ void _MapInfo_Map_ApplyGametype(string s, float pWantedType, float pThisType, fl
                s = cdr(s);
        }
 
+       if(pWantedType == MAPINFO_TYPE_KEEPAWAY)
+       {
+               sa = car(s);
+               if(sa != "")
+                       cvar_set("fraglimit", sa);
+               s = cdr(s)
+       }
+
        // rc = timelimit timelimit_qualification laps laps_teamplay
        if(pWantedType == MAPINFO_TYPE_RACE)
        {
@@ -526,6 +536,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_KEEPAWAY:        return "timelimit=20 pointlimit=30";
                default:                           return "";
        }
 }
@@ -650,6 +661,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 == "ka")         return MAPINFO_TYPE_KEEPAWAY;
        else if(t == "all")     return MAPINFO_TYPE_ALL;
        else                    return 0;
 }
@@ -670,6 +682,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_KEEPAWAY)             return "ka";
        else if(t == MAPINFO_TYPE_ALL)             return "all";
        else                                       return "";
 }
@@ -1134,6 +1147,8 @@ float MapInfo_CurrentGametype()
                return MAPINFO_TYPE_NEXBALL;
        else if(cvar("g_cts"))
                return MAPINFO_TYPE_CTS;
+       else if(cvar("g_ka"))
+               return MAPINFO_TYPE_KEEPAWAY;
        else
                return MAPINFO_TYPE_DEATHMATCH;
 }
@@ -1161,20 +1176,21 @@ string MapInfo_GetGameTypeCvar(float t)
 {
        switch(t)
        {
-               case MAPINFO_TYPE_DEATHMATCH: return "g_dm";
-               case MAPINFO_TYPE_TEAM_DEATHMATCH: return "g_tdm";
-               case MAPINFO_TYPE_DOMINATION: return "g_domination";
-               case MAPINFO_TYPE_CTF: return "g_ctf";
-               case MAPINFO_TYPE_RUNEMATCH: return "g_runematch";
-               case MAPINFO_TYPE_LMS: return "g_lms";
-               case MAPINFO_TYPE_ARENA: return "g_arena";
-               case MAPINFO_TYPE_CA: return "g_ca";
-               case MAPINFO_TYPE_KEYHUNT: return "g_kh";
-               case MAPINFO_TYPE_ASSAULT: return "g_assault";
-               case MAPINFO_TYPE_ONSLAUGHT: return "g_onslaught";
-               case MAPINFO_TYPE_RACE: return "g_race";
-               case MAPINFO_TYPE_NEXBALL: return "g_nexball";
-               case MAPINFO_TYPE_CTS: return "g_cts";
+               case MAPINFO_TYPE_DEATHMATCH:           return "g_dm";
+               case MAPINFO_TYPE_TEAM_DEATHMATCH:      return "g_tdm";
+               case MAPINFO_TYPE_DOMINATION:           return "g_domination";
+               case MAPINFO_TYPE_CTF:                  return "g_ctf";
+               case MAPINFO_TYPE_RUNEMATCH:            return "g_runematch";
+               case MAPINFO_TYPE_LMS:                  return "g_lms";
+               case MAPINFO_TYPE_ARENA:                        return "g_arena";
+               case MAPINFO_TYPE_CA:                   return "g_ca";
+               case MAPINFO_TYPE_KEYHUNT:              return "g_kh";
+               case MAPINFO_TYPE_ASSAULT:              return "g_assault";
+               case MAPINFO_TYPE_ONSLAUGHT:            return "g_onslaught";
+               case MAPINFO_TYPE_RACE:                         return "g_race";
+               case MAPINFO_TYPE_NEXBALL:              return "g_nexball";
+               case MAPINFO_TYPE_CTS:                  return "g_cts";
+               case MAPINFO_TYPE_KEEPAWAY:             return "g_ka";
                default: return "";
        }
 }
@@ -1196,6 +1212,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_ka",            (t == MAPINFO_TYPE_KEEPAWAY)          ? "1" : "0");
 }
 
 void MapInfo_LoadMap(string s)
index 1fcc0dc662783ebd056281a5732a9d75ca98a31f..64076e7121a5f91f7d4432b107aeb29cf5d90b6a 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_KEEPAWAY    = 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 3aea0e4858615ec2b5f2e8c72d4a49426daf16f5..ad810068b50638af9d10d8f27944171458e8c51e 100644 (file)
@@ -32,7 +32,7 @@ void XonoticServerCreateTab_fill(entity me)
        float n;
 
        me.TR(me);
-               n = 6;
+               n = 7;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_dm", "DM"));
                        e0 = e;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_lms", "LMS"));
@@ -41,6 +41,8 @@ void XonoticServerCreateTab_fill(entity me)
                        if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_runematch", "Runematch"));
                        if(e.checked) e0 = NULL;
+               me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_ka", "Keepaway"));
+                       if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_race", "Race"));
                        if(e.checked) e0 = NULL;
                me.TD(me, 1, me.columns / n, e = makeXonoticGametypeButton(1, "g_cts", "Race CTS"));
index bd54aa219400ad57f61fe675d8666e8c31e5062a..0d58b9c4a09947b9f4045d04edb23866e3e4ccdc 100644 (file)
@@ -28,6 +28,7 @@ CLASS(XonoticMapInfoDialog) EXTENDS(XonoticDialog)
        ATTRIB(XonoticMapInfoDialog, typeRaceLabel, entity, NULL)
        ATTRIB(XonoticMapInfoDialog, typeCTSLabel, entity, NULL)
        ATTRIB(XonoticMapInfoDialog, typeNexballLabel, entity, NULL)
+       ATTRIB(XonoticMapInfoDialog, typeKeepawayLabel, entity, NULL)
 
        ATTRIB(XonoticMapInfoDialog, currentMapIndex, float, 0)
        ATTRIB(XonoticMapInfoDialog, currentMapBSPName, string, string_null)
@@ -83,6 +84,7 @@ void XonoticMapInfoDialog_loadMapInfo(entity me, float i, entity mlb)
        me.typeRaceLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE);
        me.typeCTSLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_CTS);
        me.typeNexballLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_NEXBALL);
+       me.typeKeepawayLabel.disabled = !(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEEPAWAY);
 
        MapInfo_ClearTemps();
 }
@@ -148,6 +150,8 @@ void XonoticMapInfoDialog_fill(entity me)
                        me.typeCTSLabel = e;
                me.TD(me, 1, wgt, e = makeXonoticTextLabel(0, "Nexball"));
                        me.typeNexballLabel = e;
+               me.TD(me, 1, wgt, e = makeXonoticTextLabel(0, "Keepaway"));
+                       me.typeKeepawayLabel = e;
 
        me.gotoRC(me, me.rows - 2, 0);
                me.TD(me, 1, me.columns, e = makeXonoticTextLabel(0.5, ""));
index d369298a56cd5b679a7113934831a7878d2476ba..83b3b7fdf6381d1d1581a930606f06ce056aa192 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_ka;
 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;
index f381612c3f7843a427fa43724cc16e7919b0edec..bdadbdccc22f3e056755e0cda9607950b4e8bcda 100644 (file)
@@ -329,6 +329,7 @@ void cvar_changes_init()
                BADCVAR("g_runematch");
                BADCVAR("g_tdm");
                BADCVAR("g_nexball");
+               BADCVAR("g_ka");
                BADCVAR("teamplay");
 
                // long
diff --git a/qcsrc/server/mutators/gamemode_keepaway.qc b/qcsrc/server/mutators/gamemode_keepaway.qc
new file mode 100644 (file)
index 0000000..eb1bc4b
--- /dev/null
@@ -0,0 +1,285 @@
+void ka_Initialize()
+{
+       if(!g_keepaway) { 
+               remove(self); 
+               return; 
+       }
+       if (!self.model) {
+               self.model = "models/nexball/ball.md3"; 
+               self.scale = 1.3;
+       }
+
+       precache_model(self.model);
+       setmodel(self, self.model);
+       setsize(self, BALL_MINS, BALL_MAXS);
+       ball_scale = self.scale;
+       self.classname = "keepawayball";
+       self.damageforcescale = cvar("g_keepawayball_damageforcescale");
+       self.effects = self.effects | EF_FULLBRIGHT;
+       self.movetype = MOVETYPE_BOUNCE;
+       self.touch = ka_TouchEvent;
+       self.think = ka_SpawnBall;
+       self.nextthink = time;
+       self.flags = FL_ITEM;
+       self.reset = ka_Reset;
+       self.owner = world;
+       
+       // todo: Waypoints and radar
+       //WaypointSprite_AttachCarrier();
+}
+
+void ka_SpawnBall()
+{
+       if(MoveToRandomMapLocation(self, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+       {
+               makevectors(self.angles);
+               self.movetype = MOVETYPE_BOUNCE;
+               self.velocity = '0 0 200';
+               self.angles = '0 0 0';
+               self.solid = SOLID_TRIGGER;
+               //self.touch = ka_TouchEvent;
+               self.think = ka_SpawnBall;
+               self.nextthink = time + cvar("g_keepawayball_respawntime");
+       }
+       else
+       {
+               // sorry, can't spawn, better luck next frame
+               self.think = ka_SpawnBall;
+               self.nextthink = time;
+       }
+}
+
+void ka_TouchEvent(entity plyr)
+{
+       if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
+       {
+               self.think = ka_SpawnBall;
+               self.nextthink = time;
+               return;
+       }
+       if (!plyr) 
+               return;
+       if (!self) 
+               return;
+       if ((other.classname != "player" || other.health < 1) && (time > self.ctf_droptime + cvar("g_keepawayball_respawntime")))
+               return;
+       if (self.wait > time)
+               return;
+
+       self.owner = other;
+       other.ballcarried = self;
+       setattachment(self, other, "");
+       setorigin(self, BALL_ATTACHORG);
+       
+       self.velocity = '0 0 0';
+       self.movetype = MOVETYPE_NONE;
+       self.touch = SUB_Null;
+       self.alpha = 0.01;
+       
+       self.think = SUB_Null;
+       self.nextthink = 0;
+
+       self.glow_color = cvar("g_keepawayball_trail_color");
+       self.glowtrail = TRUE;
+       plyr.effects |= 8;
+       plyr.alpha = 0.6
+
+       bprint(other.netname, "^7 has picked up the ball!\n");
+       WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
+       WriteString(MSG_BROADCAST, strcat("\n\n", other.netname, "^7 has picked up the ball!\n"));
+       sound(self.owner, CHAN_AUTO, "keepaway/pickedup.wav", VOL_BASE, ATTN_NORM);
+       
+       PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
+
+       // todo: Waypoints and radar
+}
+
+void ka_DropEvent(entity plyr, entity ball)
+{      
+       setattachment(ball, world, "");
+       ball.movetype = MOVETYPE_BOUNCE;
+       ball.solid = SOLID_TRIGGER;
+       ball.wait = time + 1;
+       ball.ctf_droptime = time;
+       ball.think = ka_SpawnBall;
+       ball.nextthink = time + cvar("g_keepawayball_respawntime");
+       ball.touch = ka_TouchEvent;
+       plyr.effects = EF_LOWPRECISION;
+       plyr.alpha = 1.0;
+       ball.alpha = 1.0;
+       setorigin(ball, plyr.origin + '0 0 10');
+       ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+       
+       bprint(plyr.netname, "^7 has dropped the ball!\n");
+       WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
+       WriteString(MSG_BROADCAST, strcat("\n\n", plyr.netname, "^7 has dropped the ball!\n"));
+       sound(other, CHAN_AUTO, "keepaway/dropped.wav", VOL_BASE, ATTN_NORM);   
+       
+       PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1);
+       
+       // todo
+       //WaypointSprite_AttachCarrier("ka-ball", ball);
+       //WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
+       
+       ball.owner.kaballcarried = world;
+       ball.owner = world;
+}
+
+/*
+void ka_CheckWinner()
+{
+
+}
+
+MUTATOR_HOOKFUNCTION(ka_PlayerDies)
+{
+       float i;
+       entity e;
+
+       float temp_tag_players_count;
+       temp_tag_players_count = tag_players_count;
+
+       if(frag_target.tagcolor == frag_target.tagcolor_original) // if this is the first time we die... (our tagcolor remained unchanged)
+       {
+               for(i = 0; i < temp_tag_players_count; ++i) // check other players...
+               {
+                       e = tag_players[i];
+                       if(e == world) // empty slot, skip to next
+                       {
+                               if(temp_tag_players_count < TAGCOLOR_MAX - 1) // just in case
+                                       ++temp_tag_players_count;
+                               continue;
+                       }
+
+                       if(e.tagcolor == frag_target.tagcolor_original) // and see if they have our original tag color
+                       {
+                               tag_GetFragAttackers_ColorOwner();
+                               centerprint(e, strcat("^1Your master ^7", frag_target.netname, "^1 was tagged by ^7", frag_attacker.netname, " ^1with ^7", color_owner_red, " ^1color.\n"));
+                               e.tagcolor = frag_attacker.tagcolor; // if so, remove it, our tag color has now "died out" from this round and we can not win anymore. The attacker will "summon" all of our previously fragged targets, and also us.
+                               setcolor(e, 16 * e.tagcolor + e.tagcolor);
+                       }
+               }
+       }
+       else
+       {
+               frag_target.tagcolor = frag_attacker.tagcolor;
+               setcolor(frag_target, 16 * frag_target.tagcolor + frag_target.tagcolor);
+       }
+
+       tag_GetFragAttackers_ColorOwner();
+
+       if(color_owner_self)
+               color_owner_green = "^2your own";
+       centerprint(frag_attacker, strcat("^2You tagged ^7", frag_target.netname, " ^2with ^7", color_owner_green, " ^2color.\n"));
+
+       if(color_owner_self)
+               color_owner_red = "^1their own";
+       centerprint(frag_target, strcat("^1You were tagged by ^7", frag_attacker.netname, " ^1with ^7", color_owner_red, " ^1color.\n"));
+       bprint("^7", frag_target.netname, "^1 was tagged by ^7", frag_attacker.netname, " ^1with ^7", color_owner_red, " ^1color.\n");
+
+       frag_target.health = cvar("g_balance_health_start"); // "respawn" the player :P
+
+       tag_CheckWinner();
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(tag_RemovePlayer)
+{
+       if(self.tag_playernum == -1)
+               return 0; // nothing to remove
+
+       float i, j;
+       /*for (i = self.tag_playernum; i < tag_players_count; ++i)
+       {
+               tag_players[i] = tag_players[i+1];
+               tag_players[i].tag_playernum = tag_players[i].tag_playernum - 1;
+       }
+       */
+
+       tag_players[self.tag_playernum] = world;
+       tag_players_count = tag_players_count - 1;
+
+       // if other players have our color, randomize their color
+       entity e, random_player;
+       float temp_tag_players_count;
+       temp_tag_players_count = tag_players_count;
+
+       if(!next_round) // ... but ONLY if next_round isn't set. We don't care about the colors if the round has already ended
+       for(i = 0; i < temp_tag_players_count; ++i) // check other players...
+       {
+               e = tag_players[i];
+               if(e == world) // empty slot, skip to next
+               {
+                       if(temp_tag_players_count < TAGCOLOR_MAX - 1) // just in case
+                               ++temp_tag_players_count;
+                       continue;
+               }
+
+               if(e.tagcolor == self.tagcolor_original) // and see if they have our original tag color
+               {
+                       for(j = 0; j < 100; ++j) // try 100 times to find a color that isn't the same as our color. If this fails we are either damn unlucky, or there are really only players left of our color
+                       {
+                               random_player = tag_players[floor(random() * (TAGCOLOR_MAX - 1))];
+
+                               if(random_player == world) // hit empty slot, try again
+                                       continue;
+
+                               if(random_player.tagcolor != self.tagcolor_original) // break if we found another color
+                               {
+                                       break;
+                               }
+                       }
+                       e.tagcolor = random_player.tagcolor;
+                       setcolor(e, 16 * e.tagcolor + e.tagcolor);
+               }
+       }
+
+       self.tag_playernum = -1;
+
+       if(tag_players_count > 1 && time > warmup)
+               tag_CheckWinner();
+
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(tag_GiveFragsForKill)
+{
+       frag_score = 0; // no frags counted in Tag, maybe later (TODO)
+       return 1;
+}
+
+MUTATOR_HOOKFUNCTION(tag_PlayerPreThink)
+{
+       setcolor(self, 16 * self.tagcolor + self.tagcolor); // prevent cheating by changing player colors
+       return 1;
+}
+*/
+
+
+MUTATOR_DEFINITION(gamemode_ka)
+{
+       MUTATOR_HOOK(MakePlayerObserver, ka_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(ClientDisconnect, ka_RemovePlayer, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerDies, ka_PlayerDies, CBC_ORDER_ANY);
+       //MUTATOR_HOOK(PlayerSpawn, ka_PlayerSpawn, CBC_ORDER_ANY);
+       //MUTATOR_HOOK(GiveFragsForKill, ka_GiveFragsForKill, CBC_ORDER_FIRST);
+       //MUTATOR_HOOK(PlayerPreThink, ka_PlayerPreThink, 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_ka = 1;
+               ka_Initialize();
+       }
+
+       MUTATOR_ONREMOVE
+       {
+               g_ka = 0;
+               error("This is a game type and it cannot be removed at runtime.");
+       }
+
+       return 0;
+}
+
index b9064c783b852a515586ade5e0826b3323380bf9..88485d8ee21c6990276fc8161970fcf86def9f98 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_ka", ftos(g_ka));
 }
 
 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_ka = (!found && (prev != GAME_KEEPAWAY) && cvar("g_ka")));
 
                if(found)
                        break;
@@ -373,6 +375,13 @@ void InitGameplayMode()
                have_team_spawns = -1; // request team spawns
        }
 
+       if(g_ka)
+       {
+               game = GAME_KEEPAWAY;
+               gamemode_name = "Keepaway";
+               MUTATOR_ADD(gamemode_ka);
+       }
+
        if(teams_matter)
                entcs_init();
 
diff --git a/sound/keepaway/dropped.wav b/sound/keepaway/dropped.wav
new file mode 100644 (file)
index 0000000..6867468
Binary files /dev/null and b/sound/keepaway/dropped.wav differ
diff --git a/sound/keepaway/pickedup.wav b/sound/keepaway/pickedup.wav
new file mode 100644 (file)
index 0000000..55e55a4
Binary files /dev/null and b/sound/keepaway/pickedup.wav differ