1 /**
2 * @file
3 * @brief Brings new objects into the world.
4 */
5
6 /*
7 All original material Copyright (C) 2002-2013 UFO: Alien Invasion.
8
9 Original file from Quake 2 v3.21: quake2-2.31/game/g_spawn.c
10 Copyright (C) 1997-2001 Id Software, Inc.
11
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 as published by the Free Software Foundation; either version 2
15 of the License, or (at your option) any later version.
16
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
20
21 See the GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
26
27 */
28
29 #include "g_spawn.h"
30 #include "g_ai.h"
31 #include "g_client.h"
32 #include "g_edicts.h"
33 #include "g_func.h"
34 #include "g_inventory.h"
35 #include "g_mission.h"
36 #include "g_reaction.h"
37 #include "g_trigger.h"
38 #include "g_utils.h"
39 #include "g_vis.h"
40 #include "../shared/parse.h"
41 #include "../shared/keyvaluepair.h"
42
43 /* fields are needed for spawning from the entity string */
44 #define FFL_SPAWNTEMP 1
45 #define FFL_NOSPAWN 2
46
47 #define G_ValidMessage(ent) ((ent)->message && ((ent)->message[0] == '_' || strstr((ent)->message, "*msgid:") != nullptr))
48
49 /**
50 * @brief this is only used to hold entity field values that can be set from
51 * the editor, but aren't actually present in Edict during gameplay
52 */
53 typedef struct spawn_temp_s {
54 /* world vars */
55 int noRandomSpawn; /**< spawn the actors on random spawnpoints */
56 int noEquipment; /**< spawn the actors with no equipment - must be collected in the map */
57 } spawn_temp_t;
58
59 static spawn_temp_t spawnTemp;
60
61 static void SP_light(Edict* ent);
62 static void SP_dummy(Edict* ent);
63 static void SP_player_start(Edict* ent);
64 static void SP_human_start(Edict* ent);
65 static void SP_alien_start(Edict* ent);
66 static void SP_civilian_start(Edict* ent);
67 static void SP_worldspawn(Edict* ent);
68 static void SP_2x2_start(Edict* ent);
69 static void SP_civilian_target(Edict* ent);
70 static void SP_misc_model(Edict* ent);
71 static void SP_misc_item(Edict* ent);
72 static void SP_misc_mission(Edict* ent);
73 static void SP_misc_mission_aliens(Edict* ent);
74 static void SP_misc_message(Edict* ent);
75 static void SP_misc_smoke(Edict* ent);
76 static void SP_misc_fire(Edict* ent);
77 static void SP_misc_camera(Edict* ent);
78 static void SP_misc_smokestun(Edict* ent);
79
80 typedef struct spawn_s {
81 const char* name;
82 void (*spawn) (Edict* ent);
83 } spawn_t;
84
85 static const spawn_t spawns[] = {
86 {"worldspawn", SP_worldspawn},
87 {"light", SP_light},
88 {"misc_item", SP_misc_item},
89 {"misc_sound", SP_dummy},
90 {"misc_model", SP_misc_model},
91 {"misc_particle", SP_dummy},
92 {"misc_mission", SP_misc_mission},
93 {"misc_mission_aliens", SP_misc_mission_aliens},
94 {"info_player_start", SP_player_start},
95 {"info_human_start", SP_human_start},
96 {"info_alien_start", SP_alien_start},
97 {"info_civilian_start", SP_civilian_start},
98 {"info_civilian_target", SP_civilian_target},
99 {"info_2x2_start", SP_2x2_start},
100 {"info_null", SP_dummy},
101 {"func_breakable", SP_func_breakable},
102 {"func_door", SP_func_door},
103 {"func_door_sliding", SP_func_door_sliding},
104 {"func_rotating", SP_func_rotating},
105 {"trigger_nextmap", SP_trigger_nextmap},
106 {"trigger_hurt", SP_trigger_hurt},
107 {"trigger_touch", SP_trigger_touch},
108 {"trigger_rescue", SP_trigger_rescue},
109 {"misc_message", SP_misc_message},
110 {"misc_smoke", SP_misc_smoke},
111 {"misc_fire", SP_misc_fire},
112 {"misc_smokestun", SP_misc_smokestun},
113 {"misc_camera", SP_misc_camera},
114
115 {nullptr, nullptr}
116 };
117
118 /**
119 * @brief Finds the spawn function for the entity and calls it
120 */
ED_CallSpawn(Edict * ent)121 static void ED_CallSpawn (Edict* ent)
122 {
123 const spawn_t* s;
124
125 if (!ent->classname)
126 return;
127
128 /* check normal spawn functions */
129 for (s = spawns; s->name; s++) {
130 /* found it */
131 if (Q_streq(s->name, ent->classname)) {
132 s->spawn(ent);
133 return;
134 }
135 }
136
137 ent->inuse = false;
138 }
139
140 /**
141 * @brief Allocated memory for the given string in the level context (TAG_LEVEL)
142 * @note This memory is automatically freed when we close or change the level
143 * @param[in] string The string to copy
144 */
ED_NewString(const char * string)145 static char* ED_NewString (const char* string)
146 {
147 const size_t l = strlen(string) + 1;
148 char* newb = (char*)G_TagMalloc(l, TAG_LEVEL);
149 char* new_p = newb;
150
151 for (int i = 0; i < l; i++) {
152 /* check for special chars and convert them */
153 if (string[i] == '\\' && i < l - 1) {
154 i++;
155 if (string[i] == 'n')
156 *new_p++ = '\n';
157 else
158 *new_p++ = '\\';
159 } else
160 *new_p++ = string[i];
161 }
162
163 return newb;
164 }
165
166 /**
167 * @brief Takes a key/value pair and sets the binary values in an edict
168 */
ED_ParseField(const char * key,const char * value,Edict * ent)169 static void ED_ParseField (const char* key, const char* value, Edict* ent)
170 {
171 KeyValuePair kvp(key, value);
172
173 if (kvp.isKey("classname"))
174 ent->classname = ED_NewString(value);
175 else if (kvp.isKey("model"))
176 ent->model = ED_NewString(value);
177 else if (kvp.isKey("spawnflags"))
178 ent->spawnflags = kvp.asInt();
179 else if (kvp.isKey("speed"))
180 ent->speed = kvp.asInt();
181 else if (kvp.isKey("dir"))
182 ent->dir = kvp.asInt();
183 else if (kvp.isKey("active"))
184 ent->active = kvp.asBool();
185 else if (kvp.isKey("target"))
186 ent->target = ED_NewString(value);
187 else if (kvp.isKey("targetname"))
188 ent->targetname = ED_NewString(value);
189 else if (kvp.isKey("item"))
190 ent->item = ED_NewString(value);
191 else if (kvp.isKey("noise"))
192 ent->noise = ED_NewString(value);
193 else if (kvp.isKey("particle"))
194 ent->particle = ED_NewString(value);
195 else if (kvp.isKey("nextmap"))
196 ent->nextmap = ED_NewString(value);
197 else if (kvp.isKey("frame"))
198 ent->frame = kvp.asInt();
199 else if (kvp.isKey("team"))
200 ent->team = kvp.asInt();
201 else if (kvp.isKey("group"))
202 ent->group = ED_NewString(value);
203 else if (kvp.isKey("size"))
204 ent->fieldSize = kvp.asInt();
205 else if (kvp.isKey("count"))
206 ent->count = kvp.asInt();
207 else if (kvp.isKey("time"))
208 ent->time = kvp.asInt();
209 else if (kvp.isKey("health"))
210 ent->HP = kvp.asInt();
211 else if (kvp.isKey("radius"))
212 ent->radius = kvp.asInt();
213 else if (kvp.isKey("sounds"))
214 ent->sounds = kvp.asInt();
215 else if (kvp.isKey("material"))
216 ent->material = static_cast<edictMaterial_t>(kvp.asInt()); // enum !!
217 else if (kvp.isKey("light"))
218 ; // ignore
219 /** @todo This (maxteams) should also be handled server side - currently this is
220 * only done client side */
221 else if (kvp.isKey("maxteams"))
222 ; // ignore
223 else if (kvp.isKey("maxlevel"))
224 ; // ignore
225 else if (kvp.isKey("dmg"))
226 ent->dmg = kvp.asInt();
227 else if (kvp.isKey("origin"))
228 kvp.asVec3(ent->origin);
229 else if (kvp.isKey("angles"))
230 kvp.asVec3(ent->angles);
231 else if (kvp.isKey("angle"))
232 ent->angle = kvp.asFloat();
233 else if (kvp.isKey("message"))
234 ent->message = ED_NewString(value);
235
236 else if (kvp.isKey("norandomspawn"))
237 spawnTemp.noRandomSpawn = kvp.asInt();
238 else if (kvp.isKey("noequipment"))
239 spawnTemp.noEquipment = kvp.asInt();
240 }
241
242 /**
243 * @brief Parses an edict out of the given string, returning the new position
244 * @param[in] data The string to parse from
245 * @param[in] ent should be a properly initialized empty edict.
246 */
ED_ParseEdict(const char * data,Edict * ent)247 static const char* ED_ParseEdict (const char* data, Edict* ent)
248 {
249 bool init;
250 char keyname[MAX_VAR];
251
252 init = false;
253 OBJZERO(spawnTemp);
254
255 /* go through all the dictionary pairs */
256 while (1) {
257 /* parse key */
258 const char* c = Com_Parse(&data);
259 if (c[0] == '}')
260 break;
261 if (!data)
262 gi.Error("ED_ParseEntity: EOF without closing brace");
263
264 Q_strncpyz(keyname, c, sizeof(keyname));
265
266 /* parse value */
267 c = Com_Parse(&data);
268 if (!data)
269 gi.Error("ED_ParseEntity: EOF without closing brace");
270
271 if (c[0] == '}')
272 gi.Error("ED_ParseEntity: closing brace without data");
273
274 init = true;
275
276 /* keynames with a leading underscore are used for utility comments,
277 * and are immediately discarded by ufo */
278 if (keyname[0] == '_')
279 continue;
280
281 ED_ParseField(keyname, c, ent);
282 }
283
284 if (!init)
285 ent->init();
286
287 return data;
288 }
289
290 /**
291 * @brief Chain together all entities with a matching team field.
292 * All but the first will have the FL_GROUPSLAVE flag set.
293 * All but the last will have the groupchain field set to the next one
294 */
G_FindEdictGroups(void)295 static void G_FindEdictGroups (void)
296 {
297 Edict* ent = G_EdictsGetFirst(); /* the first edict is always a world edict that can be skipped */
298
299 while ((ent = G_EdictsGetNextInUse(ent))) {
300 /* no group at all */
301 if (!ent->group)
302 continue;
303 /* already marked as slave in another group */
304 if (ent->flags & FL_GROUPSLAVE)
305 continue;
306 Edict* chain = ent;
307 ent->groupMaster = ent;
308 Edict* groupMember = ent;
309 /* search only the remainder of the entities */
310 while ((groupMember = G_EdictsGetNextInUse(groupMember))) {
311 /* no group at all */
312 if (!groupMember->group)
313 continue;
314 /* already marked as slave in another group */
315 if (groupMember->flags & FL_GROUPSLAVE)
316 continue;
317 /* same group as the master? */
318 if (Q_streq(ent->group, groupMember->group)) {
319 chain->groupChain = groupMember;
320 groupMember->groupMaster = ent;
321 chain = groupMember;
322 groupMember->flags |= FL_GROUPSLAVE;
323 }
324 }
325 }
326 }
327
328 /**
329 * @brief Creates a server's entity / program execution context
330 * by parsing textual entity definitions out of an ent file.
331 * @sa CM_EntityString
332 * @sa SV_SpawnServer
333 */
G_SpawnEntities(const char * mapname,bool day,const char * entities)334 void G_SpawnEntities (const char* mapname, bool day, const char* entities)
335 {
336 int entnum;
337
338 G_FreeTags(TAG_LEVEL);
339
340 OBJZERO(level);
341 level.pathingMap = (pathing_t*)G_TagMalloc(sizeof(*level.pathingMap), TAG_LEVEL);
342
343 G_EdictsInit();
344
345 /* initialize reactionFire data */
346 G_ReactionFireTargetsInit();
347
348 Q_strncpyz(level.mapname, mapname, sizeof(level.mapname));
349 level.day = day;
350
351 G_ResetClientData();
352
353 level.activeTeam = TEAM_NO_ACTIVE;
354 level.actualRound = 1;
355 level.hurtAliens = sv_hurtaliens->integer;
356
357 /* parse ents */
358 entnum = 0;
359 while (1) {
360 Edict* ent;
361 /* parse the opening brace */
362 const char* token = Com_Parse(&entities);
363 if (!entities)
364 break;
365 if (token[0] != '{')
366 gi.Error("ED_LoadFromFile: found %s when expecting {", token);
367
368 ent = G_Spawn();
369
370 entities = ED_ParseEdict(entities, ent);
371
372 ent->mapNum = entnum++;
373
374 /* Set the position of the entity */
375 VecToPos(ent->origin, ent->pos);
376
377 /* Call this entity's specific initializer (sets ent->type) */
378 ED_CallSpawn(ent);
379
380 /* if this entity is an bbox (e.g. actor), then center its origin based on its position */
381 if (ent->solid == SOLID_BBOX)
382 G_EdictCalcOrigin(ent);
383 }
384
385 /* spawn ai players, if needed */
386 if (level.num_spawnpoints[TEAM_CIVILIAN]) {
387 if (AI_CreatePlayer(TEAM_CIVILIAN) == nullptr)
388 gi.DPrintf("Could not create civilian\n");
389 }
390
391 if ((G_IsSinglePlayer() || ai_multiplayeraliens->integer) && level.num_spawnpoints[TEAM_ALIEN]) {
392 if (AI_CreatePlayer(TEAM_ALIEN) == nullptr)
393 gi.DPrintf("Could not create alien\n");
394 }
395
396 Com_Printf("Used inventory slots after ai spawn: %i\n", game.i.GetUsedSlots());
397
398 G_FindEdictGroups();
399 }
400
401 /**
402 * @brief Either finds a free edict, or allocates a new one.
403 * @note Try to avoid reusing an entity that was recently freed, because it
404 * can cause the player to think the entity morphed into something else
405 * instead of being removed and recreated, which can cause interpolated
406 * angles and bad trails.
407 * @sa G_FreeEdict
408 */
G_Spawn(const char * classname)409 Edict* G_Spawn (const char* classname)
410 {
411 Edict* ent = G_EdictsGetNewEdict();
412
413 if (!ent)
414 gi.Error("G_Spawn: no free edicts");
415
416 ent->inuse = true;
417 ent->number = G_EdictsGetNumber(ent);
418 if (classname)
419 ent->classname = classname;
420 else
421 ent->classname = "noclass";
422 ent->fieldSize = ACTOR_SIZE_NORMAL;
423 ent->setActive(); /* only used by camera */
424 return ent;
425 }
426
Think_SmokeAndFire(Edict * self)427 static void Think_SmokeAndFire (Edict* self)
428 {
429 const int endRound = self->time + self->count;
430 const int spawnIndex = (self->team + level.teamOfs) % MAX_TEAMS;
431 const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS;
432 if (endRound < level.actualRound || (endRound == level.actualRound && spawnIndex <= currentIndex)) {
433 const bool checkVis = self->type == ET_SMOKE;
434 G_EventEdictPerish(G_VisToPM(self->particleLink->visflags), *self->particleLink);
435 G_FreeEdict(self->particleLink);
436 G_FreeEdict(self);
437 if (checkVis)
438 G_CheckVis(nullptr);
439 }
440 }
441
G_SpawnSmoke(const vec3_t vec,const char * particle,int rounds)442 static void G_SpawnSmoke (const vec3_t vec, const char* particle, int rounds)
443 {
444 pos3_t pos;
445 Edict* ent;
446
447 VecToPos(vec, pos);
448
449 ent = G_GetEdictFromPos(pos, ET_SMOKE);
450 if (ent == nullptr) {
451 pos_t z = gi.GridFall(ACTOR_SIZE_NORMAL, pos);
452 if (z != pos[2])
453 return;
454
455 ent = G_Spawn();
456 VectorCopy(pos, ent->pos);
457 G_EdictCalcOrigin(ent);
458 ent->spawnflags = G_GetLevelFlagsFromPos(pos);
459 ent->particle = particle;
460 SP_misc_smoke(ent);
461 }
462
463 ent->count = rounds;
464 }
465
466 /**
467 * @brief Spawns a smoke field that is available for some rounds
468 * @param[in] vec The position in the world that is the center of the smoke field
469 * @param[in] particle The id of the particle (see ptl_*.ufo script files in base/ufos)
470 * @param[in] rounds The number of rounds the particle will last
471 * @todo Does '2 rounds' mean: created in player's turn, last through the aliens turn, vanish before the 2nd player's turn ??
472 * @param[in] radius The max distance of a cell from the center to get a particle
473 */
G_SpawnSmokeField(const vec3_t vec,const char * particle,int rounds,vec_t radius)474 void G_SpawnSmokeField (const vec3_t vec, const char* particle, int rounds, vec_t radius)
475 {
476 vec_t x, y;
477
478 G_SpawnSmoke(vec, particle, rounds);
479
480 /* for all cells in a square of +/- radius */
481 for (x = vec[0] - radius; x <= vec[0] + radius; x += UNIT_SIZE) {
482 for (y = vec[1] - radius; y <= vec[1] + radius; y += UNIT_SIZE) {
483 vec3_t end;
484
485 VectorSet(end, x, y, vec[2]);
486
487 /* cut off the edges of the square to resemble a circle */
488 if (VectorDist(end, vec) > radius)
489 continue;
490 const trace_t tr = G_Trace(vec, end, nullptr, MASK_SMOKE_AND_FIRE);
491 /* trace didn't reach the target - something was hit before */
492 if (tr.fraction < 1.0 || (tr.contentFlags & CONTENTS_WATER)) {
493 continue;
494 }
495 G_SpawnSmoke(end, particle, rounds);
496 }
497 }
498 }
499
G_SpawnFire(const vec3_t vec,const char * particle,int rounds,int damage)500 static void G_SpawnFire (const vec3_t vec, const char* particle, int rounds, int damage)
501 {
502 pos3_t pos;
503 Edict* ent;
504
505 VecToPos(vec, pos);
506
507 ent = G_GetEdictFromPos(pos, ET_FIRE);
508 if (ent == nullptr) {
509 pos_t z = gi.GridFall(ACTOR_SIZE_NORMAL, pos);
510 if (z != pos[2])
511 return;
512
513 ent = G_Spawn();
514 VectorCopy(pos, ent->pos);
515 VectorCopy(vec, ent->origin);
516 ent->dmg = damage;
517 ent->particle = particle;
518 ent->spawnflags = G_GetLevelFlagsFromPos(pos);
519 SP_misc_fire(ent);
520 }
521
522 ent->count = rounds;
523 }
524
G_SpawnFireField(const vec3_t vec,const char * particle,int rounds,int damage,vec_t radius)525 void G_SpawnFireField (const vec3_t vec, const char* particle, int rounds, int damage, vec_t radius)
526 {
527 vec_t x, y;
528
529 G_SpawnFire(vec, particle, rounds, damage);
530
531 for (x = vec[0] - radius; x <= vec[0] + radius; x += UNIT_SIZE) {
532 for (y = vec[1] - radius; y <= vec[1] + radius; y += UNIT_SIZE) {
533 vec3_t end;
534 VectorSet(end, x, y, vec[2]);
535
536 if (VectorDist(end, vec) > radius)
537 continue;
538 const trace_t tr = G_Trace(vec, end, nullptr, MASK_SMOKE_AND_FIRE);
539 /* trace didn't reach the target - something was hit before */
540 if (tr.fraction < 1.0 || (tr.contentFlags & CONTENTS_WATER)) {
541 continue;
542 }
543
544 G_SpawnFire(end, particle, rounds, damage);
545 }
546 }
547 }
548
G_SpawnStunSmoke(const vec3_t vec,const char * particle,int rounds,int damage)549 static void G_SpawnStunSmoke (const vec3_t vec, const char* particle, int rounds, int damage)
550 {
551 pos3_t pos;
552 Edict* ent;
553
554 VecToPos(vec, pos);
555
556 ent = G_GetEdictFromPos(pos, ET_SMOKESTUN);
557 if (ent == nullptr) {
558 pos_t z = gi.GridFall(ACTOR_SIZE_NORMAL, pos);
559 if (z != pos[2])
560 return;
561
562 ent = G_Spawn();
563 VectorCopy(pos, ent->pos);
564 VectorCopy(vec, ent->origin);
565 ent->dmg = damage;
566 ent->particle = particle;
567 ent->spawnflags = G_GetLevelFlagsFromPos(pos);
568 SP_misc_smokestun(ent);
569 }
570
571 ent->count = rounds;
572 }
573
G_SpawnStunSmokeField(const vec3_t vec,const char * particle,int rounds,int damage,vec_t radius)574 void G_SpawnStunSmokeField (const vec3_t vec, const char* particle, int rounds, int damage, vec_t radius)
575 {
576 vec_t x, y;
577
578 G_SpawnStunSmoke(vec, particle, rounds, damage);
579
580 for (x = vec[0] - radius; x <= vec[0] + radius; x += UNIT_SIZE) {
581 for (y = vec[1] - radius; y <= vec[1] + radius; y += UNIT_SIZE) {
582 vec3_t end;
583 VectorSet(end, x, y, vec[2]);
584
585 if (VectorDist(end, vec) > radius)
586 continue;
587 const trace_t tr = G_Trace(vec, end, nullptr, MASK_SMOKE_AND_FIRE);
588 /* trace didn't reach the target - something was hit before */
589 if (tr.fraction < 1.0 || (tr.contentFlags & CONTENTS_WATER)) {
590 continue;
591 }
592
593 G_SpawnStunSmoke(end, particle, rounds, damage);
594 }
595 }
596 }
597
598 /**
599 * @brief Spawns a new entity at the floor
600 * @note This is e.g. used to place dropped weapons/items at the floor
601 */
G_SpawnFloor(const pos3_t pos)602 Edict* G_SpawnFloor (const pos3_t pos)
603 {
604 Edict* floorItem;
605
606 floorItem = G_Spawn("item");
607 floorItem->type = ET_ITEM;
608 /* make sure that the item is always on a field that even the smallest actor can reach */
609 floorItem->fieldSize = ACTOR_SIZE_NORMAL;
610 VectorCopy(pos, floorItem->pos);
611 floorItem->pos[2] = gi.GridFall(floorItem->fieldSize, floorItem->pos);
612 G_EdictCalcOrigin(floorItem);
613 return floorItem;
614 }
615
616 /**
617 * This is only for particles that are spawned during a match - not for map particles.
618 * @return A particle edict
619 */
G_SpawnParticle(const vec3_t origin,int spawnflags,const char * particle)620 Edict* G_SpawnParticle (const vec3_t origin, int spawnflags, const char* particle)
621 {
622 Edict* ent = G_Spawn("particle");
623 ent->type = ET_PARTICLE;
624 VectorCopy(origin, ent->origin);
625
626 /* Set the position of the entity */
627 VecToPos(ent->origin, ent->pos);
628
629 ent->particle = particle;
630 ent->spawnflags = spawnflags;
631
632 G_CheckVis(ent);
633
634 return ent;
635 }
636
637 /**
638 * @brief Spawn point for a 1x1 unit.
639 */
G_ActorSpawn(Edict * ent)640 static void G_ActorSpawn (Edict* ent)
641 {
642 /* set properties */
643 level.num_spawnpoints[ent->team]++;
644 ent->classname = "actor";
645 ent->type = ET_ACTORSPAWN;
646 ent->fieldSize = ACTOR_SIZE_NORMAL;
647
648 /* Fall to ground */
649 if (ent->pos[2] >= PATHFINDING_HEIGHT)
650 ent->pos[2] = PATHFINDING_HEIGHT - 1;
651
652 ent->pos[2] = gi.GridFall(ent->fieldSize, ent->pos);
653 if (ent->pos[2] >= PATHFINDING_HEIGHT)
654 gi.DPrintf("G_ActorSpawn: Warning: z level is out of bounds: %i\n", ent->pos[2]);
655
656 G_EdictCalcOrigin(ent);
657
658 /* link it for collision detection */
659 ent->dir = AngleToDir(ent->angle);
660 assert(ent->dir < CORE_DIRECTIONS);
661 ent->solid = SOLID_BBOX;
662
663 /* Set bounding box. Maybe this is already set in one of the spawn functions? */
664 if (ent->maxs[0] == 0)
665 VectorSet(ent->maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_STAND);
666 if (ent->mins[0] == 0)
667 VectorSet(ent->mins, -PLAYER_WIDTH, -PLAYER_WIDTH, PLAYER_MIN);
668 }
669
670 /**
671 * @brief Spawn a singleplayer 2x2 unit.
672 */
G_Actor2x2Spawn(Edict * ent)673 static void G_Actor2x2Spawn (Edict* ent)
674 {
675 /* set properties */
676 level.num_2x2spawnpoints[ent->team]++;
677 ent->classname = "ugv";
678 ent->type = ET_ACTOR2x2SPAWN;
679 ent->fieldSize = ACTOR_SIZE_2x2;
680
681 /* Spawning has already calculated the pos from the origin ( = center of the cell). Perfect for normal size actors.
682 * For 2x2 actors, the origin(of the info_ box) is in the middle of the four cells. Using VecToPos on that origin
683 * results in the upper right cell being the pos of the actor. But we want the lower left cell to be the pos of the
684 * 2x2 actor because routing and pathfinding rely on that. So compensate for that. */
685 ent->pos[0]--;
686 ent->pos[1]--;
687
688 /* Fall to ground */
689 if (ent->pos[2] >= PATHFINDING_HEIGHT)
690 ent->pos[2] = PATHFINDING_HEIGHT - 1;
691 ent->pos[2] = gi.GridFall(ent->fieldSize, ent->pos);
692 if (ent->pos[2] >= PATHFINDING_HEIGHT)
693 gi.DPrintf("G_Actor2x2Spawn: Warning: z level is out of bounds: %i\n", ent->pos[2]);
694 G_EdictCalcOrigin(ent);
695
696 /* link it for collision detection */
697 ent->dir = AngleToDir(ent->angle);
698 assert(ent->dir < CORE_DIRECTIONS);
699 ent->solid = SOLID_BBOX;
700
701 /* Set bounding box. Maybe this is already set in one of the spawn functions? */
702 if (ent->maxs[0] == 0)
703 VectorSet(ent->maxs, PLAYER2x2_WIDTH, PLAYER2x2_WIDTH, PLAYER_STAND);
704 if (ent->mins[0] == 0)
705 VectorSet(ent->mins, -PLAYER2x2_WIDTH, -PLAYER2x2_WIDTH, PLAYER_MIN);
706 }
707
708 /**
709 * @brief light (0 1 0) (-8 -8 -8) (8 8 8)
710 */
SP_light(Edict * ent)711 static void SP_light (Edict* ent)
712 {
713 /* lights aren't client-server communicated items */
714 /* they are completely client side */
715 G_FreeEdict(ent);
716 }
717
718 /**
719 * @brief info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
720 * Starting point for a player.
721 * "team" the number of the team for this player starting point
722 * "0" is reserved for civilians and critters (use info_civilian_start instead)
723 */
SP_player_start(Edict * ent)724 static void SP_player_start (Edict* ent)
725 {
726 /* only used in multi player */
727 if (G_IsSinglePlayer()) {
728 G_FreeEdict(ent);
729 return;
730 }
731
732 /** @todo Wrong place here */
733 /* maybe there are already the max soldiers allowed per team connected */
734 if (sv_maxsoldiersperteam->integer > level.num_spawnpoints[ent->team]) {
735 ent->STUN = 0;
736 ent->HP = INITIAL_HP;
737 G_ActorSpawn(ent);
738 } else
739 G_FreeEdict(ent);
740 }
741
742 /**
743 * @brief info_human_start (1 0 0) (-16 -16 -24) (16 16 32)
744 * Starting point for a single player human.
745 */
SP_human_start(Edict * ent)746 static void SP_human_start (Edict* ent)
747 {
748 /* only used in single player */
749 if (G_IsMultiPlayer()) {
750 G_FreeEdict(ent);
751 return;
752 }
753 ent->team = TEAM_PHALANX;
754 ent->STUN = 0;
755 ent->HP = INITIAL_HP;
756 G_ActorSpawn(ent);
757 }
758
759
760 /**
761 * @brief info_2x2_start (1 1 0) (-32 -32 -24) (32 32 32)
762 * Starting point for a 2x2 unit.
763 */
SP_2x2_start(Edict * ent)764 static void SP_2x2_start (Edict* ent)
765 {
766 /* no 2x2 unit in multiplayer */
767 if (G_IsMultiPlayer()) {
768 G_FreeEdict(ent);
769 return;
770 }
771 /* set stats */
772 ent->STUN = 0;
773 ent->HP = INITIAL_HP;
774
775 if (!ent->team)
776 ent->team = TEAM_PHALANX;
777
778 /* these units are bigger */
779 VectorSet(ent->maxs, PLAYER_WIDTH * 2, PLAYER_WIDTH * 2, PLAYER_STAND);
780 VectorSet(ent->mins, -(PLAYER_WIDTH * 2), -(PLAYER_WIDTH * 2), PLAYER_MIN);
781
782 /* spawn singleplayer 2x2 unit */
783 G_Actor2x2Spawn(ent);
784 }
785
786 /**
787 * @brief info_alien_start (1 0 0) (-16 -16 -24) (16 16 32)
788 * Starting point for a single player alien.
789 */
SP_alien_start(Edict * ent)790 static void SP_alien_start (Edict* ent)
791 {
792 /* deactivateable in multiplayer */
793 if (G_IsMultiPlayer() && !ai_multiplayeraliens->integer) {
794 G_FreeEdict(ent);
795 return;
796 }
797 ent->team = TEAM_ALIEN;
798 /* set stats */
799 ent->STUN = 0;
800 ent->HP = INITIAL_HP;
801
802 G_ActorSpawn(ent);
803 }
804
805
806 /**
807 * @brief info_civilian_start (0 1 1) (-16 -16 -24) (16 16 32)
808 * Starting point for a civilian.
809 */
SP_civilian_start(Edict * ent)810 static void SP_civilian_start (Edict* ent)
811 {
812 /* deactivateable in multiplayer */
813 if (G_IsMultiPlayer() && !ai_numcivilians->integer) {
814 G_FreeEdict(ent);
815 return;
816 }
817 ent->team = TEAM_CIVILIAN;
818 /* set stats */
819 ent->STUN = 99; /** @todo Does anybody know _why_ this is set to 99? */
820 ent->HP = INITIAL_HP;
821 ent->count = 100; /* current waypoint */
822 G_ActorSpawn(ent);
823 }
824
825 /**
826 * @brief info_civilian_start (0 1 1) (-16 -16 -24) (16 16 32)
827 * Way point for a civilian.
828 * @sa SP_civilian_start
829 * @todo These waypoints should be placeable by the human player (e.g. spawn a special particle on the waypoint)
830 * to direct the civilians to a special location
831 */
SP_civilian_target(Edict * ent)832 static void SP_civilian_target (Edict* ent)
833 {
834 /* target point for which team */
835 ent->team = TEAM_CIVILIAN;
836 ent->classname = "civtarget";
837 ent->type = ET_CIVILIANTARGET;
838 ent->fieldSize = ACTOR_SIZE_NORMAL; /* to let the grid fall function work */
839
840 /* add the edict to the list of known waypoints */
841 G_AddToWayPointList(ent);
842
843 /* fall to ground */
844 if (ent->pos[2] >= PATHFINDING_HEIGHT)
845 ent->pos[2] = PATHFINDING_HEIGHT - 1;
846 ent->pos[2] = gi.GridFall(ent->fieldSize, ent->pos);
847 G_EdictCalcOrigin(ent);
848 }
849
850 /**
851 * @brief Initializes the human/phalanx mission entity
852 */
SP_misc_mission(Edict * ent)853 static void SP_misc_mission (Edict* ent)
854 {
855 Edict* other;
856
857 ent->classname = "misc_mission";
858 ent->type = ET_MISSION;
859
860 /* maybe this was set to something else for multiplayer */
861 if (!ent->team)
862 ent->team = TEAM_PHALANX;
863
864 ent->solid = SOLID_BBOX;
865
866 if (ent->HP) {
867 ent->flags |= FL_DESTROYABLE;
868 ent->destroy = G_MissionDestroy;
869 }
870
871 if (!ent->HP && !ent->time && !ent->target) {
872 G_FreeEdict(ent);
873 gi.DPrintf("misc_mission given with no objective\n");
874 return;
875 }
876
877 /* think function values */
878 ent->think = G_MissionThink;
879 ent->nextthink = 1;
880
881 if (ent->radius <= GRID_WIDTH) {
882 ent->radius = GRID_WIDTH * 3;
883 }
884 VectorSet(ent->absmax, ent->radius, ent->radius, PLAYER_STAND);
885 VectorSet(ent->absmin, -ent->radius, -ent->radius, PLAYER_MIN);
886
887 if (G_ValidMessage(ent))
888 G_MissionAddVictoryMessage(ent->message);
889
890 /* spawn the trigger entity */
891 other = G_TriggerSpawn(ent);
892 other->touch = G_MissionTouch;
893 if (ent->target)
894 ent->use = G_MissionUse;
895 ent->child = other;
896
897 gi.LinkEdict(ent);
898 }
899
900 /**
901 * @brief Initializes the alien mission entity
902 */
SP_misc_mission_aliens(Edict * ent)903 static void SP_misc_mission_aliens (Edict* ent)
904 {
905 Edict* other;
906
907 ent->classname = "mission";
908 ent->type = ET_MISSION;
909 ent->team = TEAM_ALIEN;
910 ent->solid = SOLID_BBOX;
911
912 /* think function values */
913 ent->think = G_MissionThink;
914 ent->nextthink = 1;
915
916 VectorSet(ent->absmax, PLAYER_WIDTH * 3, PLAYER_WIDTH * 3, PLAYER_STAND);
917 VectorSet(ent->absmin, -(PLAYER_WIDTH * 3), -(PLAYER_WIDTH * 3), PLAYER_MIN);
918
919 /* spawn the trigger entity */
920 other = G_TriggerSpawn(ent);
921 other->touch = G_MissionTouch;
922 ent->child = other;
923
924 gi.LinkEdict(ent);
925 }
926
927 /**
928 * @note This is only working for one z-level. But our models should be
929 * split for each level anyway.
930 * @param ent The edict to fill the forbidden list for
931 */
G_BuildForbiddenListForEntity(Edict * ent)932 static void G_BuildForbiddenListForEntity (Edict* ent)
933 {
934 pos3_t mins, maxs, origin;
935 vec3_t center, shiftedMins, shiftedMaxs;
936 int xDelta, yDelta, size, i, j;
937
938 VectorAdd(ent->absmin, ent->origin, shiftedMins);
939 VectorAdd(ent->absmax, ent->origin, shiftedMaxs);
940
941 VectorCenterFromMinsMaxs(shiftedMins, shiftedMaxs, center);
942 VecToPos(shiftedMins, mins);
943 VecToPos(shiftedMaxs, maxs);
944 VecToPos(center, origin);
945
946 xDelta = std::max(1, maxs[0] - mins[0]);
947 yDelta = std::max(1, maxs[1] - mins[1]);
948
949 size = xDelta * yDelta;
950 ent->forbiddenListPos = (pos3_t*)G_TagMalloc(size * sizeof(pos3_t), TAG_LEVEL);
951 ent->forbiddenListSize = size;
952
953 for (i = 0; i < xDelta; i++) {
954 for (j = 0; j < yDelta; j++) {
955 const pos_t x = mins[0] + i;
956 const pos_t y = mins[1] + j;
957 const pos_t z = origin[2];
958 VectorSet(ent->forbiddenListPos[i], x, y, z);
959 }
960 }
961 }
962
963 #define MISC_MODEL_SOLID (1 << 8)
964 /**
965 * @brief Spawns a misc_model if there is a solid state
966 */
SP_misc_model(Edict * ent)967 static void SP_misc_model (Edict* ent)
968 {
969 if (ent->spawnflags & MISC_MODEL_SOLID) {
970 if (ent->model && ent->model[0] != '\0') {
971 AABB modelAabb;
972 if (gi.LoadModelAABB(ent->model, ent->frame, modelAabb)) {
973 ent->classname = "model";
974 VectorCopy(modelAabb.maxs, ent->maxs);
975 VectorCopy(modelAabb.mins, ent->mins);
976 ent->type = ET_SOLID;
977 ent->solid = SOLID_BBOX;
978 /** @todo is fieldsize and forbidden list update really needed here? */
979 ent->fieldSize = ACTOR_SIZE_NORMAL;
980 gi.LinkEdict(ent);
981 G_BuildForbiddenListForEntity(ent);
982 } else {
983 gi.DPrintf("Could not get mins/maxs for model '%s'\n", ent->model);
984 G_FreeEdict(ent);
985 }
986 } else {
987 gi.DPrintf("server_solid misc_model with no model given\n");
988 G_FreeEdict(ent);
989 }
990 } else {
991 /* handled client side */
992 G_FreeEdict(ent);
993 }
994 }
995
996 /**
997 * @brief Spawns an item to the ground container
998 */
SP_misc_item(Edict * ent)999 static void SP_misc_item (Edict* ent)
1000 {
1001 if (!ent->item) {
1002 gi.DPrintf("No item defined in misc_item\n");
1003 G_FreeEdict(ent);
1004 return;
1005 }
1006
1007 G_AddItemToFloor(ent->pos, ent->item);
1008
1009 /* now we can free the original edict */
1010 G_FreeEdict(ent);
1011 }
1012
Message_Use(Edict * self,Edict * activator)1013 static bool Message_Use (Edict* self, Edict* activator)
1014 {
1015 if (!activator || !G_IsActor(activator)) {
1016 return false;
1017 } else {
1018 Player &player = activator->getPlayer();
1019 const char* msg = self->message;
1020 /* remove gettext marker */
1021 if (msg[0] == '_')
1022 msg++;
1023 G_ClientPrintf(player, PRINT_HUD, "%s", msg);
1024
1025 if (self->spawnflags & 1)
1026 G_FreeEdict(self);
1027
1028 return false;
1029 }
1030 }
1031
G_SpawnField(Edict * ent,const char * classname,entity_type_t type,solid_t solid)1032 static void G_SpawnField (Edict* ent, const char* classname, entity_type_t type, solid_t solid)
1033 {
1034 vec3_t particleOrigin;
1035
1036 ent->classname = classname;
1037 ent->type = type;
1038 ent->fieldSize = ACTOR_SIZE_NORMAL;
1039 ent->solid = solid;
1040 VectorSet(ent->maxs, UNIT_SIZE / 2, UNIT_SIZE / 2, UNIT_HEIGHT / 2);
1041 VectorSet(ent->mins, -UNIT_SIZE / 2, -UNIT_SIZE / 2, -UNIT_HEIGHT / 2);
1042 G_EdictCalcOrigin(ent);
1043 ent->think = Think_SmokeAndFire;
1044 ent->nextthink = 1;
1045 ent->time = level.actualRound;
1046 ent->team = level.activeTeam;
1047
1048 gi.LinkEdict(ent);
1049
1050 VectorCopy(ent->origin, particleOrigin);
1051 particleOrigin[2] -= GROUND_DELTA;
1052 ent->particleLink = G_SpawnParticle(particleOrigin, ent->spawnflags, ent->particle);
1053 }
1054
SP_misc_smoke(Edict * ent)1055 static void SP_misc_smoke (Edict* ent)
1056 {
1057 G_SpawnField(ent, "smoke", ET_SMOKE, SOLID_NOT);
1058 G_CheckVis(nullptr);
1059 }
1060
SP_misc_fire(Edict * ent)1061 static void SP_misc_fire (Edict* ent)
1062 {
1063 G_SpawnField(ent, "fire", ET_FIRE, SOLID_TRIGGER);
1064 ent->dmgtype = gi.csi->damIncendiary;
1065 ent->touch = Touch_HurtTrigger;
1066 }
1067
SP_misc_smokestun(Edict * ent)1068 static void SP_misc_smokestun (Edict* ent)
1069 {
1070 G_SpawnField(ent, "stunsmoke", ET_SMOKESTUN, SOLID_TRIGGER);
1071 ent->dmgtype = gi.csi->damStunGas;
1072 ent->touch = Touch_HurtTrigger;
1073 }
1074
SP_misc_message(Edict * ent)1075 static void SP_misc_message (Edict* ent)
1076 {
1077 if (!ent->message) {
1078 G_FreeEdict(ent);
1079 return;
1080 }
1081
1082 if (!G_ValidMessage(ent))
1083 gi.DPrintf("No translation marker for misc_message set\n");
1084 ent->use = Message_Use;
1085 ent->classname = "misc_message";
1086 ent->type = ET_MESSAGE;
1087 ent->solid = SOLID_NOT;
1088 }
1089
1090 #define CAMERA_ROTATE (1 << 8)
1091
SP_misc_camera(Edict * ent)1092 static void SP_misc_camera (Edict* ent)
1093 {
1094 /* only used in single player */
1095 if (G_IsMultiPlayer()) {
1096 G_FreeEdict(ent);
1097 return;
1098 }
1099
1100 const bool rotate = ent->spawnflags & CAMERA_ROTATE;
1101 G_InitCamera(ent, CAMERA_STATIONARY, ent->angle, rotate);
1102 }
1103
1104 /**
1105 * @brief a dummy to get rid of local entities
1106 */
SP_dummy(Edict * ent)1107 static void SP_dummy (Edict* ent)
1108 {
1109 /* particles aren't client-server communicated items
1110 * they are completely client side */
1111 G_FreeEdict(ent);
1112 }
1113
1114 /**
1115 * @brief Spawns the world entity
1116 *
1117 * Only used for the world.
1118 * "sounds" music cd track number
1119 * "maxlevel" max. level to use in the map
1120 * "maxteams" max team amount for multiplayergames for the current map
1121 */
SP_worldspawn(Edict * ent)1122 static void SP_worldspawn (Edict* ent)
1123 {
1124 ent->solid = SOLID_BSP;
1125 /* since the world doesn't use G_Spawn() */
1126 ent->inuse = true;
1127 ent->classname = "worldspawn";
1128
1129 level.noEquipment = spawnTemp.noEquipment;
1130 level.noRandomSpawn = spawnTemp.noRandomSpawn;
1131
1132 gi.ConfigString(CS_MAXCLIENTS, "%i", sv_maxclients->integer);
1133
1134 /* only used in multi player */
1135 if (G_IsMultiPlayer()) {
1136 gi.ConfigString(CS_MAXSOLDIERSPERTEAM, "%i", sv_maxsoldiersperteam->integer);
1137 gi.ConfigString(CS_MAXSOLDIERSPERPLAYER, "%i", sv_maxsoldiersperplayer->integer);
1138 gi.ConfigString(CS_ENABLEMORALE, "%i", sv_enablemorale->integer);
1139 gi.ConfigString(CS_MAXTEAMS, "%s", sv_maxteams->string);
1140 }
1141 }
1142