]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/spawnpoints.qc
Merge branch 'master' into Mario/csqc_muzzleflash
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / spawnpoints.qc
index 3d62c149cf9996c74454ea0c0e27c04c9ad6648a..3dc76afc0df832d926703f7ffd1fa2611af803ce 100644 (file)
@@ -1,24 +1,27 @@
 #include "spawnpoints.qh"
 
-#include "mutators/_mod.qh"
+#include <server/mutators/_mod.qh>
 #include "g_world.qh"
+#include "miscfunctions.qh"
 #include "race.qh"
+#include "defs.qh"
 #include "../common/constants.qh"
 #include <common/net_linked.qh>
 #include "../common/teams.qh"
-#include "../common/triggers/subs.qh"
+#include <common/gamemodes/_mod.qh>
+#include "../common/mapobjects/subs.qh"
+#include "../common/mapobjects/target/spawnpoint.qh"
 #include "../common/util.qh"
 #include "../lib/warpzone/common.qh"
 #include "../lib/warpzone/util_server.qh"
+#include <server/utils.qh>
 
 bool SpawnPoint_Send(entity this, entity to, int sf)
 {
        WriteHeader(MSG_ENTITY, ENT_CLIENT_SPAWNPOINT);
 
        WriteByte(MSG_ENTITY, this.team);
-       WriteCoord(MSG_ENTITY, this.origin.x);
-       WriteCoord(MSG_ENTITY, this.origin.y);
-       WriteCoord(MSG_ENTITY, this.origin.z);
+       WriteVector(MSG_ENTITY, this.origin);
 
        return true;
 }
@@ -32,9 +35,7 @@ bool SpawnEvent_Send(entity this, entity to, int sf)
        if(autocvar_g_spawn_alloweffects)
        {
                WriteByte(MSG_ENTITY, etof(this.owner));
-               WriteCoord(MSG_ENTITY, this.owner.origin.x);
-               WriteCoord(MSG_ENTITY, this.owner.origin.y);
-               WriteCoord(MSG_ENTITY, this.owner.origin.z);
+               WriteVector(MSG_ENTITY, this.owner.origin);
                send = true;
        }
        else if((to == this.owner) || (IS_SPEC(to) && (to.enemy == this.owner)) )
@@ -64,11 +65,28 @@ void spawnpoint_use(entity this, entity actor, entity trigger)
        if(have_team_spawns > 0)
        {
                this.team = actor.team;
-               some_spawn_has_been_used = 1;
+               some_spawn_has_been_used = true;
+               this.SendFlags |= 1; // update team on the client side
        }
        //LOG_INFO("spawnpoint was used!\n");
 }
 
+void spawnpoint_reset(entity this)
+{
+       this.SendFlags |= 1; // update team since it was restored during reset
+}
+
+void link_spawnpoint(entity this)
+{
+       bool anypoint = (autocvar_g_spawn_useallspawns || (teamplay && have_team_spawns <= 0)); // TODO: check if available teams is equal to spawnpoints available
+
+       // Don't show team spawns in non-team matches,
+       // and don't show non-team spawns in team matches.
+       // (Unless useallspawns is activated)
+       if(anypoint || !((teamplay && !Team_IsValidTeam(this.team)) || (!teamplay && Team_IsValidTeam(this.team))))
+               Net_LinkEntity(this, false, 0, SpawnPoint_Send);
+}
+
 void relocate_spawnpoint(entity this)
 {
     // nudge off the floor
@@ -83,14 +101,15 @@ void relocate_spawnpoint(entity this)
         this.maxs = PL_MAX_CONST;
         if (!move_out_of_solid(this))
             objerror(this, "could not get out of solid at all!");
-        LOG_INFO("^1NOTE: this map needs FIXING. Spawnpoint at ", vtos(o - '0 0 1'));
-        LOG_INFO(" needs to be moved out of solid, e.g. by '", ftos(this.origin.x - o.x));
-        LOG_INFO(" ", ftos(this.origin.y - o.y));
-        LOG_INFO(" ", ftos(this.origin.z - o.z), "'\n");
+        LOG_INFOF(
+            "^1NOTE: this map needs FIXING. Spawnpoint at %s needs to be moved out of solid, e.g. by %s",
+            vtos(o - '0 0 1'),
+            vtos(this.origin - o)
+        );
         if (autocvar_g_spawnpoints_auto_move_out_of_solid)
         {
             if (!spawnpoint_nag)
-                LOG_INFO("\{1}^1NOTE: this map needs FIXING (it contains spawnpoints in solid, see server log)\n");
+                LOG_INFO("\{1}^1NOTE: this map needs FIXING (it contains spawnpoints in solid, see server log)");
             spawnpoint_nag = 1;
         }
         else
@@ -105,6 +124,7 @@ void relocate_spawnpoint(entity this)
     this.use = spawnpoint_use;
     setthink(this, spawnpoint_think);
     this.nextthink = time + 0.5 + random() * 2; // shouldn't need it for a little second
+    this.reset2 = spawnpoint_reset; // restores team, allows re-sending the spawnpoint
     this.team_saved = this.team;
     IL_PUSH(g_saved_team, this);
     if (!this.cnt)
@@ -113,46 +133,21 @@ void relocate_spawnpoint(entity this)
     if (have_team_spawns != 0)
         if (this.team)
             have_team_spawns = 1;
-    have_team_spawns_forteam[this.team] = 1;
+    have_team_spawns_forteams |= BIT(this.team);
 
     if (autocvar_r_showbboxes)
     {
         // show where spawnpoints point at too
-        makevectors(this.angles);
+        vector forward, right, up;
+        MAKE_VECTORS(this.angles, forward, right, up);
         entity e = new(info_player_foo);
-        setorigin(e, this.origin + v_forward * 24);
+        setorigin(e, this.origin + forward * 24);
         setsize(e, '-8 -8 -8', '8 8 8');
         e.solid = SOLID_TRIGGER;
     }
 
-       // Don't show team spawns in non-team matches,
-       // and don't show non-team spawns in team matches.
-       // (Unless useallspawns is activated)
-       if(
-               !(
-                       ( // if this passes, there is a DM spawn on a team match
-                               teamplay
-                               && (this.team != NUM_TEAM_1)
-                               && (this.team != NUM_TEAM_2)
-                               && (this.team != NUM_TEAM_3)
-                               && (this.team != NUM_TEAM_4)
-                       )
-                       ||
-                       ( // if this passes, there is a team spawn on a DM match
-                               !teamplay
-                               &&
-                               (
-                                       (this.team == NUM_TEAM_1)
-                                       || (this.team == NUM_TEAM_2)
-                                       || (this.team == NUM_TEAM_3)
-                                       || (this.team == NUM_TEAM_4)
-                               )
-                       )
-               )
-               ||
-               autocvar_g_spawn_useallspawns
-       )
-       { Net_LinkEntity(this, false, 0, SpawnPoint_Send); }
+    // network it after all spawnpoints are setup, so that we can check if team spawnpoints are used
+       InitializeEntity(this, link_spawnpoint, INITPRIO_FINDTARGET);
 }
 
 spawnfunc(info_player_survivor)
@@ -213,20 +208,15 @@ spawnfunc(info_player_team4)
 // Returns:
 //   _x: prio (-1 if unusable)
 //   _y: weight
-vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck)
+vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck, bool targetcheck)
 {
-       float shortest, thisdist;
-       float prio;
-
-       prio = 0;
-
        // filter out spots for the wrong team
        if(teamcheck >= 0)
                if(spot.team != teamcheck)
                        return '-1 0 0';
 
        if(race_spawns)
-               if(spot.target == "")
+               if(!spot.target || spot.target == "")
                        return '-1 0 0';
 
        if(IS_REAL_CLIENT(this))
@@ -240,19 +230,20 @@ vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck)
                        return '-1 0 0';
        }
 
-       shortest = vlen(world.maxs - world.mins);
-       FOREACH_CLIENT(IS_PLAYER(it) && it != this, LAMBDA(
-               thisdist = vlen(it.origin - spot.origin);
+       float prio = 0;
+       float shortest = vlen(world.maxs - world.mins);
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it) && it != this, {
+               float thisdist = vlen(it.origin - spot.origin);
                if (thisdist < shortest)
                        shortest = thisdist;
-       ));
+       });
        if(shortest > mindist)
                prio += SPAWN_PRIO_GOOD_DISTANCE;
 
        vector spawn_score = prio * '1 0 0' + shortest * '0 1 0';
 
        // filter out spots for assault
-       if(spot.target != "")
+       if(spot.target && spot.target != "" && targetcheck)
        {
                int found = 0;
                for(entity targ = findchain(targetname, spot.target); targ; targ = targ.chain)
@@ -266,7 +257,7 @@ vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck)
                        }
                }
 
-               if(!found)
+               if(!found && !g_cts)
                {
                        LOG_TRACE("WARNING: spawnpoint at ", vtos(spot.origin), " could not find its target ", spot.target);
                        return '-1 0 0';
@@ -278,21 +269,21 @@ vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck)
        return spawn_score;
 }
 
-void Spawn_ScoreAll(entity this, entity firstspot, float mindist, float teamcheck)
+void Spawn_ScoreAll(entity this, entity firstspot, float mindist, float teamcheck, bool targetcheck)
 {
        entity spot;
        for(spot = firstspot; spot; spot = spot.chain)
-               spot.spawnpoint_score = Spawn_Score(this, spot, mindist, teamcheck);
+               spot.spawnpoint_score = Spawn_Score(this, spot, mindist, teamcheck, targetcheck);
 }
 
-entity Spawn_FilterOutBadSpots(entity this, entity firstspot, float mindist, float teamcheck)
+entity Spawn_FilterOutBadSpots(entity this, entity firstspot, float mindist, float teamcheck, bool targetcheck)
 {
        entity spot, spotlist, spotlistend;
 
        spotlist = NULL;
        spotlistend = NULL;
 
-       Spawn_ScoreAll(this, firstspot, mindist, teamcheck);
+       Spawn_ScoreAll(this, firstspot, mindist, teamcheck, targetcheck);
 
        for(spot = firstspot; spot; spot = spot.chain)
        {
@@ -331,24 +322,34 @@ SelectSpawnPoint
 Finds a point to respawn
 =============
 */
+bool testspawn_checked;
+entity testspawn_point;
 entity SelectSpawnPoint(entity this, bool anypoint)
 {
        float teamcheck;
-       entity spot, firstspot;
+       entity spot = NULL;
+
+       if(!testspawn_checked)
+       {
+               testspawn_point = find(NULL, classname, "testplayerstart");
+               testspawn_checked = true;
+       }
 
-       spot = find(NULL, classname, "testplayerstart");
-       if (spot)
-               return spot;
+       if(testspawn_point)
+               return testspawn_point;
+
+       if(this.spawnpoint_targ)
+               return this.spawnpoint_targ;
 
        if(anypoint || autocvar_g_spawn_useallspawns)
                teamcheck = -1;
        else if(have_team_spawns > 0)
        {
-               if(have_team_spawns_forteam[this.team] == 0)
+               if(!(have_team_spawns_forteams & BIT(this.team)))
                {
                        // we request a spawn for a team, and we have team
                        // spawns, but that team has no spawns?
-                       if(have_team_spawns_forteam[0])
+                       if(have_team_spawns_forteams & BIT(0))
                                // try noteam spawns
                                teamcheck = 0;
                        else
@@ -358,7 +359,7 @@ entity SelectSpawnPoint(entity this, bool anypoint)
                else
                        teamcheck = this.team; // MUST be team
        }
-       else if(have_team_spawns == 0 && have_team_spawns_forteam[0])
+       else if(have_team_spawns == 0 && (have_team_spawns_forteams & BIT(0)))
                teamcheck = 0; // MUST be noteam
        else
                teamcheck = -1;
@@ -366,7 +367,16 @@ entity SelectSpawnPoint(entity this, bool anypoint)
 
 
        // get the entire list of spots
-       firstspot = findchain(classname, "info_player_deathmatch");
+       //entity firstspot = findchain(classname, "info_player_deathmatch");
+       entity firstspot = IL_FIRST(g_spawnpoints);
+       entity prev = NULL;
+       IL_EACH(g_spawnpoints, true,
+       {
+               if(prev)
+                       prev.chain = it;
+               it.chain = NULL;
+               prev = it;
+       });
        // filter out the bad ones
        // (note this returns the original list if none survived)
        if(anypoint)
@@ -375,7 +385,23 @@ entity SelectSpawnPoint(entity this, bool anypoint)
        }
        else
        {
-               firstspot = Spawn_FilterOutBadSpots(this, firstspot, 100, teamcheck);
+               firstspot = Spawn_FilterOutBadSpots(this, firstspot, 100, teamcheck, true);
+
+               // emergency fallback! double check without targets
+               // fixes some crashes with improperly repacked maps
+               if(!firstspot)
+               {
+                       firstspot = IL_FIRST(g_spawnpoints);
+                       prev = NULL;
+                       IL_EACH(g_spawnpoints, true,
+                       {
+                               if(prev)
+                                       prev.chain = it;
+                               it.chain = NULL;
+                               prev = it;
+                       });
+                       firstspot = Spawn_FilterOutBadSpots(this, firstspot, 100, teamcheck, false);
+               }
 
                // there is 50/50 chance of choosing a random spot or the furthest spot
                // (this means that roughly every other spawn will be furthest, so you