]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/spawnpoints.qc
Don't multiply RPC shot direction by 1000 either
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / spawnpoints.qc
1 #include "spawnpoints.qh"
2
3 #include <server/mutators/_mod.qh>
4 #include "g_world.qh"
5 #include "miscfunctions.qh"
6 #include "race.qh"
7 #include "defs.qh"
8 #include "../common/constants.qh"
9 #include <common/net_linked.qh>
10 #include "../common/teams.qh"
11 #include <common/mapinfo.qh>
12 #include "../common/mapobjects/subs.qh"
13 #include "../common/mapobjects/target/spawnpoint.qh"
14 #include "../common/util.qh"
15 #include "../lib/warpzone/common.qh"
16 #include "../lib/warpzone/util_server.qh"
17 #include <server/utils.qh>
18
19 bool SpawnPoint_Send(entity this, entity to, int sf)
20 {
21         WriteHeader(MSG_ENTITY, ENT_CLIENT_SPAWNPOINT);
22
23         WriteByte(MSG_ENTITY, this.team);
24         WriteVector(MSG_ENTITY, this.origin);
25
26         return true;
27 }
28
29 bool SpawnEvent_Send(entity this, entity to, int sf)
30 {
31         float send;
32
33         WriteHeader(MSG_ENTITY, ENT_CLIENT_SPAWNEVENT);
34
35         if(autocvar_g_spawn_alloweffects)
36         {
37                 WriteByte(MSG_ENTITY, etof(this.owner));
38                 WriteVector(MSG_ENTITY, this.owner.origin);
39                 send = true;
40         }
41         else if((to == this.owner) || (IS_SPEC(to) && (to.enemy == this.owner)) )
42         {
43                 WriteByte(MSG_ENTITY, 0);
44                 send = true;
45         }
46         else { send = false; }
47
48         return send;
49 }
50
51 .vector spawnpoint_prevorigin;
52 void spawnpoint_think(entity this)
53 {
54         this.nextthink = time + 0.1;
55         if(this.origin != this.spawnpoint_prevorigin)
56         {
57                 this.spawnpoint_prevorigin = this.origin;
58                 this.SendFlags |= 1;
59         }
60 }
61
62 void spawnpoint_use(entity this, entity actor, entity trigger)
63 {
64         if(teamplay)
65         if(have_team_spawns > 0)
66         {
67                 this.team = actor.team;
68                 some_spawn_has_been_used = true;
69                 this.SendFlags |= 1; // update team on the client side
70         }
71         //LOG_INFO("spawnpoint was used!\n");
72 }
73
74 void spawnpoint_reset(entity this)
75 {
76         this.SendFlags |= 1; // update team since it was restored during reset
77 }
78
79 void link_spawnpoint(entity this)
80 {
81         bool anypoint = (autocvar_g_spawn_useallspawns || (teamplay && have_team_spawns <= 0)); // TODO: check if available teams is equal to spawnpoints available
82
83         // Don't show team spawns in non-team matches,
84         // and don't show non-team spawns in team matches.
85         // (Unless useallspawns is activated)
86         if(anypoint || !((teamplay && !Team_IsValidTeam(this.team)) || (!teamplay && Team_IsValidTeam(this.team))))
87                 Net_LinkEntity(this, false, 0, SpawnPoint_Send);
88 }
89
90 void relocate_spawnpoint(entity this)
91 {
92     // nudge off the floor
93     setorigin(this, this.origin + '0 0 1');
94
95     tracebox(this.origin, PL_MIN_CONST, PL_MAX_CONST, this.origin, true, this);
96     if (trace_startsolid)
97     {
98         vector o;
99         o = this.origin;
100         this.mins = PL_MIN_CONST;
101         this.maxs = PL_MAX_CONST;
102         if (!move_out_of_solid(this))
103             objerror(this, "could not get out of solid at all!");
104         LOG_INFOF(
105             "^1NOTE: this map needs FIXING. Spawnpoint at %s needs to be moved out of solid, e.g. by %s",
106             vtos(o - '0 0 1'),
107             vtos(this.origin - o)
108         );
109         if (autocvar_g_spawnpoints_auto_move_out_of_solid)
110         {
111             if (!spawnpoint_nag)
112                 LOG_INFO("\{1}^1NOTE: this map needs FIXING (it contains spawnpoints in solid, see server log)");
113             spawnpoint_nag = 1;
114         }
115         else
116         {
117             setorigin(this, o);
118             this.mins = this.maxs = '0 0 0';
119             objerror(this, "player spawn point in solid, mapper sucks!\n");
120             return;
121         }
122     }
123
124     this.use = spawnpoint_use;
125     setthink(this, spawnpoint_think);
126     this.nextthink = time + 0.5 + random() * 2; // shouldn't need it for a little second
127     this.reset2 = spawnpoint_reset; // restores team, allows re-sending the spawnpoint
128     this.team_saved = this.team;
129     IL_PUSH(g_saved_team, this);
130     if (!this.cnt)
131         this.cnt = 1;
132
133     if (have_team_spawns != 0)
134         if (this.team)
135             have_team_spawns = 1;
136     have_team_spawns_forteams |= BIT(this.team);
137
138     if (autocvar_r_showbboxes)
139     {
140         // show where spawnpoints point at too
141         vector forward, right, up;
142         MAKE_VECTORS(this.angles, forward, right, up);
143         entity e = new(info_player_foo);
144         setorigin(e, this.origin + forward * 24);
145         setsize(e, '-8 -8 -8', '8 8 8');
146         e.solid = SOLID_TRIGGER;
147     }
148
149     // network it after all spawnpoints are setup, so that we can check if team spawnpoints are used
150         InitializeEntity(this, link_spawnpoint, INITPRIO_FINDTARGET);
151 }
152
153 spawnfunc(info_player_survivor)
154 {
155         spawnfunc_info_player_deathmatch(this);
156 }
157
158 spawnfunc(info_player_start)
159 {
160         spawnfunc_info_player_deathmatch(this);
161 }
162
163 spawnfunc(info_player_deathmatch)
164 {
165         this.classname = "info_player_deathmatch";
166         IL_PUSH(g_spawnpoints, this);
167         relocate_spawnpoint(this);
168 }
169
170 /*QUAKED spawnfunc_info_player_team1 (1 0 0) (-16 -16 -24) (16 16 24)
171 Starting point for a player in team one (Red).
172 Keys: "angle" viewing angle when spawning. */
173 spawnfunc(info_player_team1)
174 {
175         this.team = NUM_TEAM_1; // red
176         spawnfunc_info_player_deathmatch(this);
177 }
178
179
180 /*QUAKED spawnfunc_info_player_team2 (1 0 0) (-16 -16 -24) (16 16 24)
181 Starting point for a player in team two (Blue).
182 Keys: "angle" viewing angle when spawning. */
183 spawnfunc(info_player_team2)
184 {
185         this.team = NUM_TEAM_2; // blue
186         spawnfunc_info_player_deathmatch(this);
187 }
188
189 /*QUAKED spawnfunc_info_player_team3 (1 0 0) (-16 -16 -24) (16 16 24)
190 Starting point for a player in team three (Yellow).
191 Keys: "angle" viewing angle when spawning. */
192 spawnfunc(info_player_team3)
193 {
194         this.team = NUM_TEAM_3; // yellow
195         spawnfunc_info_player_deathmatch(this);
196 }
197
198
199 /*QUAKED spawnfunc_info_player_team4 (1 0 0) (-16 -16 -24) (16 16 24)
200 Starting point for a player in team four (Purple).
201 Keys: "angle" viewing angle when spawning. */
202 spawnfunc(info_player_team4)
203 {
204         this.team = NUM_TEAM_4; // purple
205         spawnfunc_info_player_deathmatch(this);
206 }
207
208 // Returns:
209 //   _x: prio (-1 if unusable)
210 //   _y: weight
211 vector Spawn_Score(entity this, entity spot, float mindist, float teamcheck, bool targetcheck)
212 {
213         // filter out spots for the wrong team
214         if(teamcheck >= 0)
215                 if(spot.team != teamcheck)
216                         return '-1 0 0';
217
218         if(race_spawns)
219                 if(!spot.target || spot.target == "")
220                         return '-1 0 0';
221
222         if(IS_REAL_CLIENT(this))
223         {
224                 if(spot.restriction == 1)
225                         return '-1 0 0';
226         }
227         else
228         {
229                 if(spot.restriction == 2)
230                         return '-1 0 0';
231         }
232
233         float prio = 0;
234         float shortest = vlen(world.maxs - world.mins);
235         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it) && it != this, {
236                 float thisdist = vlen(it.origin - spot.origin);
237                 if (thisdist < shortest)
238                         shortest = thisdist;
239         });
240         if(shortest > mindist)
241                 prio += SPAWN_PRIO_GOOD_DISTANCE;
242
243         vector spawn_score = prio * '1 0 0' + shortest * '0 1 0';
244
245         // filter out spots for assault
246         if(spot.target && spot.target != "" && targetcheck)
247         {
248                 int found = 0;
249                 for(entity targ = findchain(targetname, spot.target); targ; targ = targ.chain)
250                 {
251                         ++found;
252                         if(targ.spawn_evalfunc)
253                         {
254                                 spawn_score = targ.spawn_evalfunc(targ, this, spot, spawn_score);
255                                 if(spawn_score.x < 0)
256                                         return spawn_score;
257                         }
258                 }
259
260                 if(!found && !g_cts)
261                 {
262                         LOG_TRACE("WARNING: spawnpoint at ", vtos(spot.origin), " could not find its target ", spot.target);
263                         return '-1 0 0';
264                 }
265         }
266
267         MUTATOR_CALLHOOK(Spawn_Score, this, spot, spawn_score);
268         spawn_score = M_ARGV(2, vector);
269         return spawn_score;
270 }
271
272 void Spawn_ScoreAll(entity this, entity firstspot, float mindist, float teamcheck, bool targetcheck)
273 {
274         entity spot;
275         for(spot = firstspot; spot; spot = spot.chain)
276                 spot.spawnpoint_score = Spawn_Score(this, spot, mindist, teamcheck, targetcheck);
277 }
278
279 entity Spawn_FilterOutBadSpots(entity this, entity firstspot, float mindist, float teamcheck, bool targetcheck)
280 {
281         entity spot, spotlist, spotlistend;
282
283         spotlist = NULL;
284         spotlistend = NULL;
285
286         Spawn_ScoreAll(this, firstspot, mindist, teamcheck, targetcheck);
287
288         for(spot = firstspot; spot; spot = spot.chain)
289         {
290                 if(spot.spawnpoint_score.x >= 0) // spawning allowed here
291                 {
292                         if(spotlistend)
293                                 spotlistend.chain = spot;
294                         spotlistend = spot;
295                         if(!spotlist)
296                                 spotlist = spot;
297                 }
298         }
299         if(spotlistend)
300                 spotlistend.chain = NULL;
301
302         return spotlist;
303 }
304
305 entity Spawn_WeightedPoint(entity firstspot, float lower, float upper, float exponent)
306 {
307         // weight of a point: bound(lower, mindisttoplayer, upper)^exponent
308         // multiplied by spot.cnt (useful if you distribute many spawnpoints in a small area)
309         entity spot;
310
311         RandomSelection_Init();
312         for(spot = firstspot; spot; spot = spot.chain)
313                 RandomSelection_AddEnt(spot, (bound(lower, spot.spawnpoint_score.y, upper) ** exponent) * spot.cnt, (spot.spawnpoint_score.y >= lower) * 0.5 + spot.spawnpoint_score.x);
314
315         return RandomSelection_chosen_ent;
316 }
317
318 /*
319 =============
320 SelectSpawnPoint
321
322 Finds a point to respawn
323 =============
324 */
325 bool testspawn_checked;
326 entity testspawn_point;
327 entity SelectSpawnPoint(entity this, bool anypoint)
328 {
329         float teamcheck;
330         entity spot = NULL;
331
332         if(!testspawn_checked)
333         {
334                 testspawn_point = find(NULL, classname, "testplayerstart");
335                 testspawn_checked = true;
336         }
337
338         if(testspawn_point)
339                 return testspawn_point;
340
341         if(this.spawnpoint_targ)
342                 return this.spawnpoint_targ;
343
344         if(anypoint || autocvar_g_spawn_useallspawns)
345                 teamcheck = -1;
346         else if(have_team_spawns > 0)
347         {
348                 if(!(have_team_spawns_forteams & BIT(this.team)))
349                 {
350                         // we request a spawn for a team, and we have team
351                         // spawns, but that team has no spawns?
352                         if(have_team_spawns_forteams & BIT(0))
353                                 // try noteam spawns
354                                 teamcheck = 0;
355                         else
356                                 // if not, any spawn has to do
357                                 teamcheck = -1;
358                 }
359                 else
360                         teamcheck = this.team; // MUST be team
361         }
362         else if(have_team_spawns == 0 && (have_team_spawns_forteams & BIT(0)))
363                 teamcheck = 0; // MUST be noteam
364         else
365                 teamcheck = -1;
366                 // if we get here, we either require team spawns but have none, or we require non-team spawns and have none; use any spawn then
367
368
369         // get the entire list of spots
370         //entity firstspot = findchain(classname, "info_player_deathmatch");
371         entity firstspot = IL_FIRST(g_spawnpoints);
372         entity prev = NULL;
373         IL_EACH(g_spawnpoints, true,
374         {
375                 if(prev)
376                         prev.chain = it;
377                 it.chain = NULL;
378                 prev = it;
379         });
380         // filter out the bad ones
381         // (note this returns the original list if none survived)
382         if(anypoint)
383         {
384                 spot = Spawn_WeightedPoint(firstspot, 1, 1, 1);
385         }
386         else
387         {
388                 firstspot = Spawn_FilterOutBadSpots(this, firstspot, 100, teamcheck, true);
389
390                 // emergency fallback! double check without targets
391                 // fixes some crashes with improperly repacked maps
392                 if(!firstspot)
393                 {
394                         firstspot = IL_FIRST(g_spawnpoints);
395                         prev = NULL;
396                         IL_EACH(g_spawnpoints, true,
397                         {
398                                 if(prev)
399                                         prev.chain = it;
400                                 it.chain = NULL;
401                                 prev = it;
402                         });
403                         firstspot = Spawn_FilterOutBadSpots(this, firstspot, 100, teamcheck, false);
404                 }
405
406                 // there is 50/50 chance of choosing a random spot or the furthest spot
407                 // (this means that roughly every other spawn will be furthest, so you
408                 // usually won't get fragged at spawn twice in a row)
409                 if (random() > autocvar_g_spawn_furthest)
410                         spot = Spawn_WeightedPoint(firstspot, 1, 1, 1);
411                 else
412                         spot = Spawn_WeightedPoint(firstspot, 1, 5000, 5); // chooses a far far away spawnpoint
413         }
414
415         if (!spot)
416         {
417                 if(autocvar_spawn_debug)
418                         GotoNextMap(0);
419                 else
420                 {
421                         if(some_spawn_has_been_used)
422                                 return NULL; // team can't spawn any more, because of actions of other team
423                         else
424                                 error("Cannot find a spawn point - please fix the map!");
425                 }
426         }
427
428         return spot;
429 }