]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_keepaway.qc
Merge branch 'master' into samual/keepaway
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_keepaway.qc
index 27fb396ad2c7f86867b94296796cfa6d53885740..04939e8398fe42dc06e7bdc98ed41c347c76279e 100644 (file)
@@ -1,54 +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()
+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");
-       
-       entity e;
-       e = spawn();
-       e.think = ka_SpawnBall;
-       e.nextthink = time;
+       precache_sound("keepaway/respawn.wav");
+       precache_sound("keepaway/touch.wav");
+
+       ScoreRules_keepaway();
+       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 = autocvar_g_keepawayball_damageforcescale;
+       e.takedamage = DAMAGE_YES;
+       e.glow_color = autocvar_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);
@@ -56,222 +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");
+               self.nextthink = time + autocvar_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 = autocvar_g_keepawayball_trail_color;
+       other.glow_trail = TRUE;
+       other.effects |= EF_DIMLIGHT;
+       other.alpha = autocvar_g_keepaway_ballcarrier_alpha;
+       other.exteriorweaponentity.alpha = autocvar_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'); // FIX ME: If a player becomes spectator, the hook function is given AFTER this happens, which means the origin given is after they already moved to another position, not where they died!
-               ball.velocity = '0 0 200' + '0 100 0'*crandom() + '100 0 0'*crandom();
-       
-               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;
-       }
-       return 1;
-}
+       entity ball;
+       ball = plyr.ballcarried;
 
-/*
-void ka_DropEvent(entity plyr, entity ball)
-{      
+       if(!ball) { return; }
+       
+       // reset the 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.solid = SOLID_TRIGGER; // is this needed? 
+       ball.wait = time + 1; 
+       ball.think = ka_RespawnBall;
+       ball.nextthink = time + autocvar_g_keepawayball_respawntime;
        ball.touch = ka_TouchEvent;
-       plyr.effects = EF_LOWPRECISION;
-       plyr.alpha = 1.0;
-       ball.alpha = 1.0;
+       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;
+       
+       // reset the player effects
+       plyr.glow_trail = FALSE;
+       plyr.effects &~= EF_DIMLIGHT;
+       plyr.alpha = default_player_alpha;
+       plyr.exteriorweaponentity.alpha = default_weapon_alpha; 
        
-       bprint(plyr.netname, "^7 has dropped the ball!\n");
+       // 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_NORM);   
+       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);
        
-       // todo
-       //WaypointSprite_AttachCarrier("ka-ball", ball);
-       //WaypointSprite_Kill(plyr.waypointsprite_attachedforcarrier);
-       
-       ball.owner.kaballcarried = world;
-       ball.owner = world;
+       // 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);
 }
 
-
-void ka_CheckWinner()
+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_PlayerDies)
+MUTATOR_HOOKFUNCTION(ka_RemovePlayer)
 {
-       float i;
-       entity e;
-
-       float temp_tag_players_count;
-       temp_tag_players_count = tag_players_count;
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has left the match, drop it
+       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_Scoring)
+{
+       if((frag_attacker != frag_target) && (frag_attacker.classname == "player"))
        {
-               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);
-                       }
+               if(frag_target.ballcarried) { // add to amount of times killing carrier
+                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_CARRIERKILLS, 1);
+                       if(autocvar_g_keepaway_bckillscore) // add bckills to the score
+                               PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
                }
-       }
-       else
-       {
-               frag_target.tagcolor = frag_attacker.tagcolor;
-               setcolor(frag_target, 16 * frag_target.tagcolor + frag_target.tagcolor);
-       }
+               else if(!frag_attacker.ballcarried)
+                       if(autocvar_g_keepaway_noncarrier_warn)
+                               centerprint_atprio(frag_attacker, (CENTERPRIO_SPAM + 5), "Killing people while you don't have the ball gives no points!");
 
-       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
+               if(frag_attacker.ballcarried) // add to amount of kills while ballcarrier
+                       PlayerScore_Add(frag_attacker, SP_KEEPAWAY_SCORE, 1);
+       }
 
-       tag_CheckWinner();
+       if(self.ballcarried) { ka_DropEvent(self); } // a player with the ball has died, drop it
+       return 0;
+}
 
-       return 1;
+MUTATOR_HOOKFUNCTION(ka_GiveFragsForKill)
+{
+       frag_score = 0; // no frags counted in keepaway
+       return 0;
 }
 
-MUTATOR_HOOKFUNCTION(tag_RemovePlayer)
+MUTATOR_HOOKFUNCTION(ka_PlayerPreThink)
 {
+       // 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;
 
-       return 1;
+       // drop the ball if the player presses the use button
+       if(self.BUTTON_USE)
+               if(self.ballcarried) { ka_DropEvent(self); } 
+
+       return 0;
 }
 
-MUTATOR_HOOKFUNCTION(tag_GiveFragsForKill)
+MUTATOR_HOOKFUNCTION(ka_PlayerDamage) // for changing damage and force values that are applied to players in g_damage.qc
 {
-       frag_score = 0; // no frags counted in Tag, maybe later (TODO)
-       return 1;
+       if(frag_attacker.ballcarried) // if the attacker is a ballcarrier
+       {
+               if(frag_target == frag_attacker) // damage done to yourself
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_ballcarrier_selfforce;
+               }
+               else // damage done to noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_ballcarrier_damage;
+                       frag_force *= autocvar_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 *= autocvar_g_keepaway_noncarrier_selfdamage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_selfforce;
+               }
+               else // damage done to other noncarriers
+               {
+                       frag_damage *= autocvar_g_keepaway_noncarrier_damage;
+                       frag_force *= autocvar_g_keepaway_noncarrier_force;
+               }
+       }
+       return 0;
 }
 
-MUTATOR_HOOKFUNCTION(tag_PlayerPreThink)
+MUTATOR_HOOKFUNCTION(ka_PlayerPowerups)
 {
-       setcolor(self, 16 * self.tagcolor + self.tagcolor); // prevent cheating by changing player colors
-       return 1;
+       // 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(self.ballcarried)
+       { 
+               // force the default ballcarrier alpha on the player if they have the ball
+               self.alpha = autocvar_g_keepaway_ballcarrier_alpha;
+               self.exteriorweaponentity.alpha = autocvar_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
+       {
+               // 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;
+       }
+       
+       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
        {
@@ -289,4 +346,3 @@ MUTATOR_DEFINITION(gamemode_keepaway)
 
        return 0;
 }
-