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