]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/spawnpoints.qc
Merge remote-tracking branch 'origin/master' into samual/respawn_improvements
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / spawnpoints.qc
1 float Spawn_Send(entity to, float sf)
2 {
3         WriteByte(MSG_ENTITY, ENT_CLIENT_SPAWNPOINT);
4         WriteByte(MSG_ENTITY, sf);
5         
6         if(sf & 1)
7         {
8                 WriteByte(MSG_ENTITY, self.team);
9                 WriteShort(MSG_ENTITY, self.origin_x);
10                 WriteShort(MSG_ENTITY, self.origin_y);
11                 WriteShort(MSG_ENTITY, self.origin_z);
12         }
13         if(sf & 2)
14         {
15                 WriteLong(MSG_ENTITY, self.last_spawn_time);
16         }
17         
18         return TRUE;
19 }
20
21 void Spawn_Think(void)
22 {
23         self.nextthink = 0;
24         if(self.send_spawn < 0)
25         {
26                 self.SendFlags |= 1;
27         }
28         else
29         {
30                 self.last_spawn_time = self.send_spawn;
31                 self.SendFlags |= 2;
32         }
33 }
34
35 void spawnpoint_use()
36 {
37         if(teamplay)
38         if(have_team_spawns > 0)
39         {
40                 self.team = activator.team;
41                 some_spawn_has_been_used = 1;
42         }
43         print("spawnpoint was used!\n");
44         self.send_spawn = time;
45         self.nextthink = time;
46 }
47
48 void relocate_spawnpoint()
49 {
50     // nudge off the floor
51     setorigin(self, self.origin + '0 0 1');
52
53     tracebox(self.origin, PL_MIN, PL_MAX, self.origin, TRUE, self);
54     if (trace_startsolid)
55     {
56         vector o;
57         o = self.origin;
58         self.mins = PL_MIN;
59         self.maxs = PL_MAX;
60         if (!move_out_of_solid(self))
61             objerror("could not get out of solid at all!");
62         print("^1NOTE: this map needs FIXING. Spawnpoint at ", vtos(o - '0 0 1'));
63         print(" needs to be moved out of solid, e.g. by '", ftos(self.origin_x - o_x));
64         print(" ", ftos(self.origin_y - o_y));
65         print(" ", ftos(self.origin_z - o_z), "'\n");
66         if (autocvar_g_spawnpoints_auto_move_out_of_solid)
67         {
68             if (!spawnpoint_nag)
69                 print("\{1}^1NOTE: this map needs FIXING (it contains spawnpoints in solid, see server log)\n");
70             spawnpoint_nag = 1;
71         }
72         else
73         {
74             setorigin(self, o);
75             self.mins = self.maxs = '0 0 0';
76             objerror("player spawn point in solid, mapper sucks!\n");
77             return;
78         }
79     }
80
81     self.use = spawnpoint_use;
82     self.team_saved = self.team;
83     if (!self.cnt)
84         self.cnt = 1;
85
86     if (have_team_spawns != 0)
87         if (self.team)
88             have_team_spawns = 1;
89     have_team_spawns_forteam[self.team] = 1;
90
91     if (autocvar_r_showbboxes)
92     {
93         // show where spawnpoints point at too
94         makevectors(self.angles);
95         entity e;
96         e = spawn();
97         e.classname = "info_player_foo";
98         setorigin(e, self.origin + v_forward * 24);
99         setsize(e, '-8 -8 -8', '8 8 8');
100         e.solid = SOLID_TRIGGER;
101     }
102
103         self.think = Spawn_Think;
104         self.nextthink = time;
105
106     Net_LinkEntity(self, FALSE, 0, Spawn_Send);
107 }
108
109 void spawnfunc_info_player_survivor (void)
110 {
111         spawnfunc_info_player_deathmatch();
112 }
113
114 void spawnfunc_info_player_start (void)
115 {
116         spawnfunc_info_player_deathmatch();
117 }
118
119 void spawnfunc_info_player_deathmatch (void)
120 {
121         self.classname = "info_player_deathmatch";
122         relocate_spawnpoint();
123 }
124
125 // Returns:
126 //   _x: prio (-1 if unusable)
127 //   _y: weight
128 vector Spawn_Score(entity spot, float mindist, float teamcheck)
129 {
130         float shortest, thisdist;
131         float prio;
132         entity player;
133
134         prio = 0;
135
136         // filter out spots for the wrong team
137         if(teamcheck >= 0)
138                 if(spot.team != teamcheck)
139                         return '-1 0 0';
140
141         if(race_spawns)
142                 if(spot.target == "")
143                         return '-1 0 0';
144
145         if(clienttype(self) == CLIENTTYPE_REAL)
146         {
147                 if(spot.restriction == 1)
148                         return '-1 0 0';
149         }
150         else
151         {
152                 if(spot.restriction == 2)
153                         return '-1 0 0';
154         }
155
156         shortest = vlen(world.maxs - world.mins);
157         FOR_EACH_PLAYER(player) if (player != self)
158         {
159                 thisdist = vlen(player.origin - spot.origin);
160                 if (thisdist < shortest)
161                         shortest = thisdist;
162         }
163         if(shortest > mindist)
164                 prio += SPAWN_PRIO_GOOD_DISTANCE;
165
166         spawn_score = prio * '1 0 0' + shortest * '0 1 0';
167         spawn_spot = spot;
168
169         // filter out spots for assault
170         if(spot.target != "") {
171                 entity ent;
172                 float found;
173
174                 found = 0;
175                 for(ent = world; (ent = find(ent, targetname, spot.target)); )
176                 {
177                         ++found;
178                         if(ent.spawn_evalfunc)
179                         {
180                                 entity oldself = self;
181                                 self = ent;
182                                 spawn_score = ent.spawn_evalfunc(oldself, spot, spawn_score);
183                                 self = oldself;
184                                 if(spawn_score_x < 0)
185                                         return spawn_score;
186                         }
187                 }
188
189                 if(!found)
190                 {
191                         dprint("WARNING: spawnpoint at ", vtos(spot.origin), " could not find its target ", spot.target, "\n");
192                         return '-1 0 0';
193                 }
194         }
195
196         MUTATOR_CALLHOOK(Spawn_Score);
197         return spawn_score;
198 }
199
200 void Spawn_ScoreAll(entity firstspot, float mindist, float teamcheck)
201 {
202         entity spot;
203         for(spot = firstspot; spot; spot = spot.chain)
204                 spot.spawnpoint_score = Spawn_Score(spot, mindist, teamcheck);
205 }
206
207 entity Spawn_FilterOutBadSpots(entity firstspot, float mindist, float teamcheck)
208 {
209         entity spot, spotlist, spotlistend;
210
211         spotlist = world;
212         spotlistend = world;
213
214         Spawn_ScoreAll(firstspot, mindist, teamcheck);
215
216         for(spot = firstspot; spot; spot = spot.chain)
217         {
218                 if(spot.spawnpoint_score_x >= 0) // spawning allowed here
219                 {
220                         if(spotlistend)
221                                 spotlistend.chain = spot;
222                         spotlistend = spot;
223                         if(!spotlist)
224                                 spotlist = spot;
225                 }
226         }
227         if(spotlistend)
228                 spotlistend.chain = world;
229
230         return spotlist;
231 }
232
233 entity Spawn_WeightedPoint(entity firstspot, float lower, float upper, float exponent)
234 {
235         // weight of a point: bound(lower, mindisttoplayer, upper)^exponent
236         // multiplied by spot.cnt (useful if you distribute many spawnpoints in a small area)
237         entity spot;
238
239         RandomSelection_Init();
240         for(spot = firstspot; spot; spot = spot.chain)
241                 RandomSelection_Add(spot, 0, string_null, pow(bound(lower, spot.spawnpoint_score_y, upper), exponent) * spot.cnt, (spot.spawnpoint_score_y >= lower) * 0.5 + spot.spawnpoint_score_x);
242
243         return RandomSelection_chosen_ent;
244 }
245
246 /*
247 =============
248 SelectSpawnPoint
249
250 Finds a point to respawn
251 =============
252 */
253 entity SelectSpawnPoint (float anypoint)
254 {
255         float teamcheck;
256         entity spot, firstspot;
257
258         spot = find (world, classname, "testplayerstart");
259         if (spot)
260                 return spot;
261
262         if(anypoint || autocvar_g_spawn_useallspawns)
263                 teamcheck = -1;
264         else if(have_team_spawns > 0)
265         {
266                 if(have_team_spawns_forteam[self.team] == 0)
267                 {
268                         // we request a spawn for a team, and we have team
269                         // spawns, but that team has no spawns?
270                         if(have_team_spawns_forteam[0])
271                                 // try noteam spawns
272                                 teamcheck = 0;
273                         else
274                                 // if not, any spawn has to do
275                                 teamcheck = -1;
276                 }
277                 else
278                         teamcheck = self.team; // MUST be team
279         }
280         else if(have_team_spawns == 0 && have_team_spawns_forteam[0])
281                 teamcheck = 0; // MUST be noteam
282         else
283                 teamcheck = -1;
284                 // 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
285
286
287         // get the entire list of spots
288         firstspot = findchain(classname, "info_player_deathmatch");
289         // filter out the bad ones
290         // (note this returns the original list if none survived)
291         if(anypoint)
292         {
293                 spot = Spawn_WeightedPoint(firstspot, 1, 1, 1);
294         }
295         else
296         {
297                 float mindist;
298                 if(g_arena && arena_roundbased)
299                         mindist = 800;
300                 else
301                         mindist = 100;
302                 firstspot = Spawn_FilterOutBadSpots(firstspot, mindist, teamcheck);
303
304                 // there is 50/50 chance of choosing a random spot or the furthest spot
305                 // (this means that roughly every other spawn will be furthest, so you
306                 // usually won't get fragged at spawn twice in a row)
307                 if (random() > autocvar_g_spawn_furthest)
308                         spot = Spawn_WeightedPoint(firstspot, 1, 1, 1);
309                 else
310                         spot = Spawn_WeightedPoint(firstspot, 1, 5000, 5); // chooses a far far away spawnpoint
311         }
312
313         if (!spot)
314         {
315                 if(autocvar_spawn_debug)
316                         GotoNextMap(0);
317                 else
318                 {
319                         if(some_spawn_has_been_used)
320                                 return world; // team can't spawn any more, because of actions of other team
321                         else
322                                 error("Cannot find a spawn point - please fix the map!");
323                 }
324         }
325
326         return spot;
327 }