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