]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_keepaway.qc
Merge remote branch 'origin/master' into samual/keepaway
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_keepaway.qc
index 378c45b5e31d3ef91d30535c7763cb7ba114975c..104af5475e713118ff167a74ad4dec109711678d 100644 (file)
@@ -1,58 +1,62 @@
 void ka_SpawnBall(void);
-void ka_TouchEvent(entity);
+void ka_TouchEvent(void);
 void ka_RespawnBall(void);
+void ka_DropEvent(entity);
 
-void ka_Initialize()
-{
-       print("^4ka_Initialize was just called!\n");
+float ka_ballcarrier_waypointsprite_visible_for_player(entity);
 
+void ka_Initialize() // run at the start of a match, initiates game mode
+{
        if(!g_keepaway)
                return;
                
        precache_sound("keepaway/pickedup.wav");
        precache_sound("keepaway/dropped.wav");
+       precache_sound("keepaway/respawn.wav");
+       precache_sound("keepaway/touch.wav");
 
        ScoreRules_keepaway();
-       
-       entity e;
-       e = spawn();
-       e.think = ka_SpawnBall;
-       e.nextthink = time;
+       ka_SpawnBall();
 }
 
-void ka_SpawnBall() // self = the ball
+void ka_Reset() // used to clear the ballcarrier whenever the match switches from warmup to normal
 {
-       if(!g_keepaway) { 
-               remove(self); 
-               return; 
-       }
-       if (!self.model) {
-               self.model = "models/orbs/orbblue.md3"; 
-               self.scale = 1;
-       }
+       if(self.owner)
+               if(self.owner.classname == "player")
+                       ka_DropEvent(self.owner);
+
+       ka_RespawnBall();
+}
 
-       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_RespawnBall;
-       self.nextthink = time;
-       self.flags = FL_ITEM;
-       //self.reset = ka_Reset;
-       self.owner = world;
+void ka_SpawnBall() // loads various values for the ball, runs only once at start of match
+{
+       if(!g_keepaway) { return; }
        
-       // todo: Waypoints and radar
-       WaypointSprite_AttachCarrier("nb-ball", self);
-       //bprint("^4ka_SpawnBall was just called!\n");
+       entity e;
+       e = spawn();
+       e.model = "models/orbs/orbblue.md3";    
+       e.scale = 1;
+       precache_model(e.model);
+       setmodel(e, e.model);
+       setsize(e, '-16 -16 -20', '16 16 20'); // 20 20 20 was too big, player is only 16 16 24... gotta cheat with the Z (20) axis so that the particle isn't cut off
+       e.classname = "keepawayball";
+       e.damageforcescale = cvar("g_keepawayball_damageforcescale");
+       e.takedamage = DAMAGE_YES;
+       e.glow_color = cvar("g_keepawayball_trail_color");
+       e.glow_trail = TRUE;
+       e.movetype = MOVETYPE_BOUNCE;
+       e.touch = ka_TouchEvent;
+       e.flags = FL_ITEM;
+       e.reset = ka_Reset;
+       e.owner = world;
+
+       InitializeEntity(e, ka_RespawnBall, INITPRIO_SETLOCATION); // is this the right priority? Neh, I have no idea.. Well-- it works! So. 
 }
 
-void ka_RespawnBall()
+void ka_RespawnBall() // runs whenever the ball needs to be relocated
 {
+       vector oldballorigin = self.origin;
+
        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);
@@ -60,196 +64,271 @@ void ka_RespawnBall()
                self.velocity = '0 0 200';
                self.angles = '0 0 0';
                self.solid = SOLID_TRIGGER;
-               //self.touch = ka_TouchEvent;
                self.think = ka_RespawnBall;
                self.nextthink = time + cvar("g_keepawayball_respawntime");
+               
+               pointparticles(particleeffectnum("electro_combo"), oldballorigin, '0 0 0', 1);
+               pointparticles(particleeffectnum("electro_combo"), self.origin, '0 0 0', 1);
+
+               WaypointSprite_Spawn("ka-ball", 0, 0, self, '0 0 64', world, self.team, self, waypointsprite_attachedforcarrier, FALSE);
+               WaypointSprite_UpdateTeamRadar(self.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '0 1 1');
+               WaypointSprite_Ping(self.waypointsprite_attachedforcarrier);    
+
+               sound(self, CHAN_AUTO, "keepaway/respawn.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere) 
        }
        else
        {
-               // sorry, can't spawn, better luck next frame
-               self.think = ka_RespawnBall;
-               self.nextthink = time;
+               ka_RespawnBall(); // finding a location failed, retry 
        }
-       //bprint("^4ka_RespawnBall was just called!\n");
 }
 
-void ka_TouchEvent(entity plyr)
+void ka_TouchEvent() // runs any time that the ball comes in contact with something
 {
+       if(!self) { return; }
        if(trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT)
-       {
-               self.think = ka_SpawnBall;
-               self.nextthink = time;
+       { // The ball fell off the map, respawn it since players can't get to it
+               ka_RespawnBall();
                return;
        }
-       if(!plyr) 
-               return;
-       if(!self) 
-               return;
-       if(other.classname != "player" || other.health < 1)
-               return;
-       if(self.wait > time)
-               return;
-       //if(time > self.ctf_droptime + cvar("g_keepawayball_respawntime"))
-       //      return;
+       if(other.deadflag != DEAD_NO) { return; }
+       if(other.classname != "player") 
+       {  // The ball just touched an object, most likely the world
+               pointparticles(particleeffectnum("kaball_sparks"), self.origin, '0 0 0', 1);
+               sound(self, CHAN_AUTO, "keepaway/touch.wav", VOL_BASE, ATTN_NORM);
+               return; 
+       }
+       else if(self.wait > time) { return; }
 
+       // attach the ball to the player
        self.owner = other;
-       other.kaballcarried = self;
+       other.ballcarried = self;
        setattachment(self, other, "");
-       setorigin(self, BALL_ATTACHORG);
+       setorigin(self, '3 0 20'); // wtf why is this not '0 0 0' ? 
        
+       // make the ball invisible/unable to do anything
        self.velocity = '0 0 0';
        self.movetype = MOVETYPE_NONE;
        self.touch = SUB_Null;
-       self.alpha = 0.01;
-       
+       self.effects |= EF_NODRAW;
        self.think = SUB_Null;
        self.nextthink = 0;
+       self.takedamage = DAMAGE_NO;
 
-       self.glow_color = cvar("g_keepawayball_trail_color");
-       self.glow_trail = TRUE;
-       plyr.effects |= 8;
-       plyr.alpha = 0.6;
+       // apply effects to player
+       other.glow_color = cvar("g_keepawayball_trail_color");
+       other.glow_trail = TRUE;
+       other.effects |= EF_DIMLIGHT;
+       other.alpha = cvar("g_keepaway_ballcarrier_alpha");
+       other.exteriorweaponentity.alpha = cvar("g_keepaway_ballcarrier_alpha");
 
-       bprint(other.netname, "^7 has picked up the ball!\n");
+       // messages and sounds
+       Send_KillNotification(other.netname, "", "", KA_PICKUPBALL, MSG_KA);
        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);
+       sound(self.owner, CHAN_AUTO, "keepaway/pickedup.wav", VOL_BASE, ATTN_NONE); // ATTN_NONE (it's a sound intended to be heard anywhere) 
        
+       // scoring
        PlayerScore_Add(other, SP_KEEPAWAY_PICKUPS, 1);
 
-       // todo: Waypoints and radar
+       // waypoints
+       WaypointSprite_AttachCarrier("ka-ballcarrier", other);
+       other.waypointsprite_attachedforcarrier.waypointsprite_visible_for_player = ka_ballcarrier_waypointsprite_visible_for_player;
+       WaypointSprite_UpdateRule(other.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_UpdateTeamRadar(other.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '1 0 0');
+       WaypointSprite_Ping(other.waypointsprite_attachedforcarrier);   
+       WaypointSprite_Kill(self.waypointsprite_attachedforcarrier);
 }
 
-MUTATOR_HOOKFUNCTION(ka_RemovePlayer)
+void ka_DropEvent(entity plyr) // runs any time that a player is supposed to lose the ball
 {
-       if(self.kaballcarried) {
-               entity ball;
-               ball = self.kaballcarried;
-
-               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;
-               self.effects = EF_LOWPRECISION;
-               self.alpha = 1.0;
-               ball.alpha = 1.0;
-               setorigin(ball, self.origin + '0 0 10');
-               ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+       entity ball;
+       ball = plyr.ballcarried;
+
+       if(!ball) { return; }
        
-               bprint(self.netname, "^7 has dropped the ball!\n");
-               WriteByte(MSG_BROADCAST, SVC_CENTERPRINT);
-               WriteString(MSG_BROADCAST, strcat("\n\n", self.netname, "^7 has dropped the ball!\n"));
-               sound(other, CHAN_AUTO, "keepaway/dropped.wav", VOL_BASE, ATTN_NORM);   
-               
-               PlayerScore_Add(self, SP_KEEPAWAY_DROPS, 1);
-               
-               // todo
-               WaypointSprite_AttachCarrier("nb-ball", ball);
-               WaypointSprite_Kill(self.waypointsprite_attachedforcarrier);
-               
-               ball.owner.kaballcarried = world;
-               ball.owner = world;
-       }
+       // reset the ball
+       setattachment(ball, world, "");
+       ball.movetype = MOVETYPE_BOUNCE;
+       ball.solid = SOLID_TRIGGER; // is this needed? 
+       ball.wait = time + 1; 
+       ball.think = ka_RespawnBall;
+       ball.nextthink = time + cvar("g_keepawayball_respawntime");
+       ball.touch = ka_TouchEvent;
+       ball.takedamage = DAMAGE_YES;
+       ball.effects &~= EF_NODRAW; 
+       setorigin(ball, plyr.origin + '0 0 10');
+       ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
+       ball.owner.ballcarried = world; // I hope nothing checks to see if the world has the ball in the rest of my code :P 
+       ball.owner = world;
        
-       if((frag_attacker || frag_target) && !(frag_attacker == frag_target)) {
-               if(frag_target.kaballcarried) // get amount of times killing carrier
-                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
-               else if not(frag_attacker.kaballcarried)
-                       if(cvar("g_keepaway_noncarrier_warn"))
-                               centerprint_atprio(frag_attacker, (CENTERPRIO_SPAM + 5), "Killing people while you don't have the ball gives no points!");
+       // reset the player effects
+       plyr.glow_trail = FALSE;
+       plyr.effects &~= EF_DIMLIGHT;
+       plyr.alpha = default_player_alpha;
+       plyr.exteriorweaponentity.alpha = default_weapon_alpha; 
        
-               if(frag_attacker.kaballcarried) // get kills as carrier
-                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
-       }
-       return 1;
+       // messages and sounds
+       Send_KillNotification(plyr.netname, "", "", KA_DROPBALL, MSG_KA);
+       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_NONE);   // ATTN_NONE (it's a sound intended to be heard anywhere) 
+       
+       // scoring
+       PlayerScore_Add(plyr, SP_KEEPAWAY_DROPS, 1);
+       
+       // waypoints
+       WaypointSprite_Spawn("ka-ball", 0, 0, ball, '0 0 64', world, ball.team, ball, waypointsprite_attachedforcarrier, FALSE);
+       WaypointSprite_UpdateRule(ball.waypointsprite_attachedforcarrier, 0, SPRITERULE_DEFAULT);
+       WaypointSprite_UpdateTeamRadar(ball.waypointsprite_attachedforcarrier, RADARICON_FLAGCARRIER, '0 1 1');
+       WaypointSprite_Ping(ball.waypointsprite_attachedforcarrier);    
+       WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
+}
+
+float ka_ballcarrier_waypointsprite_visible_for_player(entity e) // runs on waypoints which are attached to ballcarriers, updates once per frame 
+{
+       if(e.ballcarried)
+               if(other.classname == "spectator") 
+                       return FALSE; // we don't want spectators of the ballcarrier to see the attached waypoint on the top of their screen
+               
+       // TODO: Make the ballcarrier lack a waypointsprite whenever they have the invisibility powerup
+
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(ka_RemovePlayer)
+{
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
+       return 0;
 }
-/*
+
 MUTATOR_HOOKFUNCTION(ka_Scoring)
 {
-       if not(frag_attacker == frag_target)
+       if((frag_attacker != frag_target) && (frag_attacker.classname == "player"))
        {
-               if(frag_target.kaballcarried) { // get amount of times killing carrier
+               if(frag_target.ballcarried) { // add to amount of times killing carrier
                        PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
-                       //ka_RemovePlayer();
+                       if(cvar("g_keepaway_bckillscore")) // add bckills to the score
+                               PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
                }
-               else if not(frag_attacker.kaballcarried)
+               else if(!frag_attacker.ballcarried)
                        if(cvar("g_keepaway_noncarrier_warn"))
                                centerprint_atprio(frag_attacker, (CENTERPRIO_SPAM + 5), "Killing people while you don't have the ball gives no points!");
 
-               if(frag_attacker.kaballcarried) // get kills as carrier
+               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
                        PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
        }
-       return 1;
+
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it
+       return 0;
 }
 
+MUTATOR_HOOKFUNCTION(ka_GiveFragsForKill)
+{
+       frag_score = 0; // no frags counted in keepaway
+       return 0;
+}
 
-MUTATOR_HOOKFUNCTION(ka_PlayerDies)
+MUTATOR_HOOKFUNCTION(ka_PlayerPreThink)
 {
-       float i;
-       entity e;
+       // clear the item used for the ball in keepaway
+       self.items &~= IT_KEY1;
+       
+       // if the player has the ball, make sure they have the item for it (Used for HUD primarily)
+       if(self.ballcarried)
+               self.items |= IT_KEY1;
+
+       // drop the ball if the player presses the use button
+       if(self.BUTTON_USE)
+               if(self.ballcarried) { ka_DropEvent(self); } 
 
-       float temp_tag_players_count;
-       temp_tag_players_count = tag_players_count;
+       return 0;
+}
 
-       if(frag_target.tagcolor == frag_target.tagcolor_original) // if this is the first time we die... (our tagcolor remained unchanged)
+MUTATOR_HOOKFUNCTION(ka_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
+{
+       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
        {
-               for(i = 0; i < temp_tag_players_count; ++i) // check other players...
+               if(frag_target == frag_attacker) // damage done to yourself
                {
-                       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;
-                       }
+                       frag_damage *= cvar("g_keepaway_ballcarrier_selfdamage");
+                       frag_force *= cvar("g_keepaway_ballcarrier_selfforce");
+               }
+               else // damage done to noncarriers
+               {
+                       frag_damage *= cvar("g_keepaway_ballcarrier_damage");
+                       frag_force *= cvar("g_keepaway_ballcarrier_force");
+               }
+       }
+       else if not(frag_target.ballcarried) // if the target is a noncarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= cvar("g_keepaway_noncarrier_selfdamage");
+                       frag_force *= cvar("g_keepaway_noncarrier_selfforce");
+               }
+               else // damage done to other noncarriers
+               {
+                       frag_damage *= cvar("g_keepaway_noncarrier_damage");
+                       frag_force *= cvar("g_keepaway_noncarrier_force");
+               }
+       }
+       return 0;
+}
+
+MUTATOR_HOOKFUNCTION(ka_PlayerPowerups)
+{
+       // right now this hook doesn't make much sense (It's actually useless this way except for minstagib invisibility alpha) 
+       // but in the future it's supposed to allow me to do some extra stuff with waypointsprites and invisibility powerup
+       // So bare with me until I can fix a certain bug with ka_ballcarrier_waypointsprite_visible_for_player() 
+       
+       // also note that this structure makes no sense (Rather there is a better way to do it) the way it's currently applied
+       // again just bare with me as this is for a future feature. 
 
-                       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);
+       if(self.ballcarried)
+       { 
+               // force the default ballcarrier alpha on the player if they have the ball
+               self.alpha = cvar("g_keepaway_ballcarrier_alpha");
+               self.exteriorweaponentity.alpha = cvar("g_keepaway_ballcarrier_alpha");
+       
+               if(g_minstagib)
+               {
+                       if(olditems & IT_STRENGTH) 
+                       { // if the player has the ball and they also have the invisibility powerup, apply alpha accordingly
+                               self.alpha = g_minstagib_invis_alpha;
+                               self.exteriorweaponentity.alpha = g_minstagib_invis_alpha;
                        }
                }
        }
+       else if(g_minstagib)
+       {
+               // if we're in minstagib and a noncarrier has invisibility, assure that we apply the invisibility effects normally
+               if(olditems & IT_STRENGTH) 
+               {
+                       self.alpha = g_minstagib_invis_alpha;
+                       self.exteriorweaponentity.alpha = g_minstagib_invis_alpha;
+               }
+       }
        else
        {
-               frag_target.tagcolor = frag_attacker.tagcolor;
-               setcolor(frag_target, 16 * frag_target.tagcolor + frag_target.tagcolor);
+               // if we're a normal player with no powerups that edit alpha make sure the alpha is default. 
+               // (normal powerups just use EF_ADDITIVE)
+               self.alpha = default_player_alpha;
+               self.exteriorweaponentity.alpha = default_weapon_alpha;
        }
-
-       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;
+       
+       return 0;
 }
-*/
-
 
 MUTATOR_DEFINITION(gamemode_keepaway)
 {
+       // I don't quite understand these orders, perhaps someone could enlighten me?
        MUTATOR_HOOK(MakePlayerObserver, ka_RemovePlayer, CBC_ORDER_ANY);
        MUTATOR_HOOK(ClientDisconnect, ka_RemovePlayer, CBC_ORDER_ANY);
-       MUTATOR_HOOK(PlayerDies, ka_RemovePlayer, 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_HOOK(PlayerDies, ka_Scoring, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GiveFragsForKill, ka_GiveFragsForKill, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPreThink, ka_PlayerPreThink, CBC_ORDER_FIRST);
+       MUTATOR_HOOK(PlayerDamage_Calculate, ka_PlayerDamage, CBC_ORDER_ANY);
+       MUTATOR_HOOK(PlayerPowerups, ka_PlayerPowerups, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {
@@ -266,5 +345,4 @@ MUTATOR_DEFINITION(gamemode_keepaway)
        }
 
        return 0;
-}
-
+}
\ No newline at end of file