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