1 /**
2  * @file
3  * @brief Local entity management.
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "../client.h"
27 #include "cl_localentity.h"
28 #include "../sound/s_main.h"
29 #include "../sound/s_sample.h"
30 #include "cl_particle.h"
31 #include "cl_actor.h"
32 #include "cl_hud.h"
33 #include "../renderer/r_mesh_anim.h"
34 #include "../renderer/r_draw.h"
35 #include "../../common/tracing.h"
36 #include "../../common/grid.h"
37 
38 cvar_t* cl_le_debug;
39 cvar_t* cl_trace_debug;
40 cvar_t* cl_map_draw_rescue_zone;
41 
42 /*===========================================================================
43 Local Model (LM) handling
44 =========================================================================== */
45 
LE_GenerateInlineModelList(void)46 static inline void LE_GenerateInlineModelList (void)
47 {
48 	le_t* le = nullptr;
49 	int i = 0;
50 
51 	while ((le = LE_GetNextInUse(le))) {
52 		if (le->model1 && le->inlineModelName[0] == '*')
53 			cl.leInlineModelList[i++] = le->inlineModelName;
54 	}
55 	cl.leInlineModelList[i] = nullptr;
56 }
57 
CL_GridRecalcRouting(const le_t * le)58 static void CL_GridRecalcRouting (const le_t* le)
59 {
60 	const cBspModel_t* model;
61 	vec3_t minVec, maxVec;
62 
63 	/* We ALWAYS check against a model, even if it isn't in use.
64 	 * An unused model is NOT included in the inline list, so it doesn't get
65 	 * traced against. */
66 	if (!le->model1 || le->inlineModelName[0] != '*')
67 		return;
68 
69 	if (Com_ServerState())
70 		return;
71 
72 	model = CM_InlineModel(cl.mapTiles, le->inlineModelName);
73 	if (!model) {
74 		return;
75 	}
76 	VectorAdd(model->mins, model->origin, minVec);
77 	VectorAdd(model->maxs, model->origin, maxVec);
78 	GridBox reroute(minVec, maxVec);
79 
80 	Grid_RecalcRouting(cl.mapTiles, cl.mapData->routing, le->inlineModelName, reroute, cl.leInlineModelList);
81 }
82 
83 /**
84  * @sa G_CompleteRecalcRouting
85  */
CL_CompleteRecalcRouting(void)86 void CL_CompleteRecalcRouting (void)
87 {
88 	le_t* le;
89 	int i;
90 
91 	LE_GenerateInlineModelList();
92 
93 	for (i = 0, le = cl.LEs; i < cl.numLEs; i++, le++)
94 		CL_GridRecalcRouting(le);
95 }
96 
97 /**
98  * @sa CL_Explode
99  * @param[in] le A local entity of a brush model (func_breakable, func_door, ...)
100  */
CL_RecalcRouting(const le_t * le)101 void CL_RecalcRouting (const le_t* le)
102 {
103 	LE_GenerateInlineModelList();
104 
105 	CL_GridRecalcRouting(le);
106 
107 	CL_ActorConditionalMoveCalc(selActor);
108 }
109 
LM_AddToSceneOrder(bool parents)110 static void LM_AddToSceneOrder (bool parents)
111 {
112 	localModel_t* lm;
113 	int i;
114 
115 	for (i = 0, lm = cl.LMs; i < cl.numLMs; i++, lm++) {
116 		if (!lm->inuse)
117 			continue;
118 
119 		/* check for visibility */
120 		if (!((1 << cl_worldlevel->integer) & lm->levelflags))
121 			continue;
122 
123 		/* if we want to render the parents and this is a child (has a parent assigned)
124 		 * then skip it */
125 		if (parents && lm->parent)
126 			continue;
127 
128 		/* if we want to render the children and this is a parent (no further parent
129 		 * assigned), then skip it. */
130 		if (!parents && lm->parent == nullptr)
131 			continue;
132 
133 		/* set entity values */
134 		entity_t ent(RF_NONE);
135 		assert(lm->model);
136 		ent.model = lm->model;
137 		ent.skinnum = lm->skin;
138 		ent.lighting = &lm->lighting;
139 		VectorCopy(lm->scale, ent.scale);
140 
141 		if (lm->parent) {
142 			/** @todo what if the tagent is not rendered due to different level flags? */
143 			ent.tagent = R_GetEntity(lm->parent->renderEntityNum);
144 			if (ent.tagent == nullptr)
145 				Com_Error(ERR_DROP, "Invalid parent entity num for local model (%s/%s): %i",
146 						lm->model->name, lm->id, lm->parent->renderEntityNum);
147 			ent.tagname = lm->tagname;
148 		} else {
149 			R_EntitySetOrigin(&ent, lm->origin);
150 			VectorCopy(lm->origin, ent.oldorigin);
151 			VectorCopy(lm->angles, ent.angles);
152 
153 			if (lm->animname[0] != '\0') {
154 				ent.as = lm->as;
155 				/* do animation */
156 				R_AnimRun(&lm->as, ent.model, cls.frametime * 1000);
157 			} else {
158 				ent.as.frame = lm->frame;
159 			}
160 		}
161 
162 		/* renderflags like RF_PULSE */
163 		ent.flags = lm->renderFlags;
164 
165 		/* add it to the scene */
166 		lm->renderEntityNum = R_AddEntity(&ent);
167 	}
168 }
169 
170 /**
171  * @brief Add the local models to the scene
172  * @sa CL_ViewRender
173  * @sa LE_AddToScene
174  * @sa LM_AddModel
175  */
LM_AddToScene(void)176 void LM_AddToScene (void)
177 {
178 	LM_AddToSceneOrder(true);
179 	LM_AddToSceneOrder(false);
180 }
181 
182 /**
183  * @brief Checks whether a local model with the same entity number is already registered
184  */
LM_Find(int entnum)185 static inline localModel_t* LM_Find (int entnum)
186 {
187 	int i;
188 
189 	for (i = 0; i < cl.numLMs; i++)
190 		if (cl.LMs[i].entnum == entnum)
191 			return &cl.LMs[i];
192 
193 	return nullptr;
194 }
195 
196 /**
197  * @brief link any floor container into the actor temp floor container
198  */
LE_LinkFloorContainer(le_t * le)199 void LE_LinkFloorContainer (le_t* le)
200 {
201 	le_t* floorItem = LE_Find(ET_ITEM, le->pos);
202 	if (floorItem)
203 		le->setFloor(floorItem);
204 	else
205 		le->resetFloor();
206 }
207 
208 /**
209  * @brief Checks whether the given le is a living actor
210  * @param[in] le The local entity to perform the check for
211  * @sa G_IsLivingActor
212  */
LE_IsActor(const le_t * le)213 bool LE_IsActor (const le_t* le)
214 {
215 	assert(le);
216 	return le->type == ET_ACTOR || le->type == ET_ACTOR2x2 || le->type == ET_ACTORHIDDEN;
217 }
218 
219 /**
220  * @brief Checks whether the given le is a living actor (but might be hidden)
221  * @param[in] le The local entity to perform the check for
222  * @sa G_IsLivingActor
223  * @sa LE_IsActor
224  */
LE_IsLivingActor(const le_t * le)225 bool LE_IsLivingActor (const le_t* le)
226 {
227 	assert(le);
228 	return LE_IsActor(le) && (LE_IsStunned(le) || !LE_IsDead(le));
229 }
230 
231 /**
232  * @brief Checks whether the given le is a living and visible actor
233  * @param[in] le The local entity to perform the check for
234  * @sa G_IsLivingActor
235  * @sa LE_IsActor
236  */
LE_IsLivingAndVisibleActor(const le_t * le)237 bool LE_IsLivingAndVisibleActor (const le_t* le)
238 {
239 	assert(le);
240 	if (LE_IsInvisible(le))
241 		return false;
242 
243 	assert(le->type != ET_ACTORHIDDEN);
244 
245 	return LE_IsLivingActor(le);
246 }
247 
248 /**
249  * @brief Register misc_models
250  * @sa CL_ViewLoadMedia
251  */
LM_Register(void)252 void LM_Register (void)
253 {
254 	localModel_t* lm;
255 	int i;
256 
257 	for (i = 0, lm = cl.LMs; i < cl.numLMs; i++, lm++) {
258 		/* register the model */
259 		lm->model = R_FindModel(lm->name);
260 		if (lm->animname[0]) {
261 			R_AnimChange(&lm->as, lm->model, lm->animname);
262 			if (!lm->as.change)
263 				Com_Printf("LM_Register: Could not change anim of %s to '%s'\n",
264 						lm->name, lm->animname);
265 		}
266 		if (!lm->model)
267 			lm->inuse = false;
268 	}
269 }
270 
LE_SetThink(le_t * le,localEntityThinkFunc_t think)271 void LE_SetThink (le_t* le, localEntityThinkFunc_t think)
272 {
273 	le->think = think;
274 }
275 
LM_GetByID(const char * id)276 localModel_t* LM_GetByID (const char* id)
277 {
278 	int i;
279 
280 	if (Q_strnull(id))
281 		return nullptr;
282 
283 	for (i = 0; i < cl.numLMs; i++) {
284 		localModel_t* lm = &cl.LMs[i];
285 		if (Q_streq(lm->id, id))
286 			return lm;
287 	}
288 	return nullptr;
289 }
290 
291 /**
292  * @brief Prepares local (not known or handled by the server) models to the map, which will be added later in LM_AddToScene().
293  * @param[in] model The model name.
294  * @param[in] origin Origin of the model (position on map).
295  * @param[in] angles Angles of the model (how it should be rotated after adding to map).
296  * @param[in] scale Scaling of the model (how it should be scaled after adding to map).
297  * @param[in] entnum Entity number.
298  * @param[in] levelflags The levels in which the entity resides/is visible.
299  * @param[in] renderFlags The flags for the renderer, eg. 'translucent'.
300  * @note misc_model
301  * @sa CL_SpawnParseEntitystring
302  * @sa LM_AddToScene
303  */
LM_AddModel(const char * model,const vec3_t origin,const vec3_t angles,int entnum,int levelflags,int renderFlags,const vec3_t scale)304 localModel_t* LM_AddModel (const char* model, const vec3_t origin, const vec3_t angles, int entnum, int levelflags, int renderFlags, const vec3_t scale)
305 {
306 	localModel_t* lm;
307 
308 	lm = &cl.LMs[cl.numLMs++];
309 
310 	if (cl.numLMs >= MAX_LOCALMODELS)
311 		Com_Error(ERR_DROP, "Too many local models\n");
312 
313 	OBJZERO(*lm);
314 	Q_strncpyz(lm->name, model, sizeof(lm->name));
315 	VectorCopy(origin, lm->origin);
316 	VectorCopy(angles, lm->angles);
317 	/* check whether there is already a model with that number */
318 	if (LM_Find(entnum))
319 		Com_Error(ERR_DROP, "Already a local model with the same id (%i) loaded\n", entnum);
320 	lm->entnum = entnum;
321 	lm->levelflags = levelflags;
322 	lm->renderFlags = renderFlags;
323 	lm->inuse = true;
324 	VectorCopy(scale, lm->scale);
325 
326 	return lm;
327 }
328 
329 /*===========================================================================
330 LE thinking
331 =========================================================================== */
332 
333 /**
334  * @brief Call think function for the given local entity if its still in use
335  */
LE_ExecuteThink(le_t * le)336 void LE_ExecuteThink (le_t* le)
337 {
338 	if (le->inuse && le->think) {
339 		le->think(le);
340 	}
341 }
342 
343 /**
344  * @brief Calls the le think function and updates the animation. The animation updated even if the
345  * particular local entity is invisible for the client. This ensures, that an animation is always
346  * lerped correctly and won't magically start again once the local entity gets visible again.
347  * @sa LET_StartIdle
348  * @sa LET_PathMove
349  * @sa LET_StartPathMove
350  * @sa LET_Projectile
351  */
LE_Think(void)352 void LE_Think (void)
353 {
354 	le_t* le = nullptr;
355 
356 	if (cls.state != ca_active)
357 		return;
358 
359 	while ((le = LE_GetNext(le))) {
360 		LE_ExecuteThink(le);
361 		/* do animation - even for invisible entities */
362 		R_AnimRun(&le->as, le->model1, cls.frametime * 1000);
363 	}
364 }
365 
LM_Think(void)366 void LM_Think (void)
367 {
368 	int i;
369 	localModel_t* lm;
370 
371 	for (i = 0, lm = cl.LMs; i < cl.numLMs; i++, lm++) {
372 		if (lm->think)
373 			lm->think(lm);
374 	}
375 }
376 
377 
378 /*===========================================================================
379  LE think functions
380 =========================================================================== */
381 
382 /**
383  * @brief Get the correct animation for the given actor state and weapons
384  * @param[in] anim Type of animation (for example "stand", "walk")
385  * @param[in] right ods index to determine the weapon in the actors right hand
386  * @param[in] left ods index to determine the weapon in the actors left hand
387  * @param[in] state the actors state - e.g. STATE_CROUCHED (crouched animations)
388  * have a 'c' in front of their animation definitions (see *.anm files for
389  * characters)
390  */
LE_GetAnim(const char * anim,int right,int left,int state)391 const char* LE_GetAnim (const char* anim, int right, int left, int state)
392 {
393 	if (!anim)
394 		return "";
395 
396 	static char retAnim[MAX_VAR];
397 	char* mod = retAnim;
398 	size_t length = sizeof(retAnim);
399 
400 	/* add crouched flag */
401 	if (state & STATE_CROUCHED) {
402 		*mod++ = 'c';
403 		length--;
404 	}
405 
406 	/* determine relevant data */
407 	char        animationIndex;
408 	char const* type;
409 	if (right == NONE) {
410 		animationIndex = '0';
411 		if (left == NONE)
412 			type = "item";
413 		else {
414 			type = INVSH_GetItemByIDX(left)->type;
415 			/* left hand grenades look OK with default anim; others don't */
416 			if (!Q_streq(type, "grenade")) {
417 				type = "pistol_d";
418 			}
419 		}
420 	} else {
421 		const objDef_t* od = INVSH_GetItemByIDX(right);
422 		animationIndex = od->animationIndex;
423 		type = od->type;
424 		if (left != NONE && Q_streq(od->type, "pistol") && Q_streq(INVSH_GetItemByIDX(left)->type, "pistol")) {
425 			type = "pistol_d";
426 		}
427 	}
428 
429 	if (Q_strstart(anim, "stand") || Q_strstart(anim, "walk")) {
430 		Com_sprintf(mod, length, "%s%c", anim, animationIndex);
431 	} else {
432 		Com_sprintf(mod, length, "%s_%s", anim, type);
433 	}
434 
435 	return retAnim;
436 }
437 
438 /**
439  * @brief Change the animation of an actor to the idle animation (which can be
440  * panic, dead or stand)
441  * @note We have more than one animation for dead - the index is given by the
442  * state of the local entity
443  * @note Think function
444  * @note See the *.anm files in the models dir
445  */
LET_StartIdle(le_t * le)446 void LET_StartIdle (le_t* le)
447 {
448 	/* hidden actors don't have models assigned, thus we can not change the
449 	 * animation for any model */
450 	if (le->type != ET_ACTORHIDDEN) {
451 		if (LE_IsDead(le))
452 			R_AnimChange(&le->as, le->model1, va("dead%i", LE_GetAnimationIndexForDeath(le)));
453 		else if (LE_IsPanicked(le))
454 			R_AnimChange(&le->as, le->model1, "panic0");
455 		else
456 			R_AnimChange(&le->as, le->model1, LE_GetAnim("stand", le->right, le->left, le->state));
457 	}
458 
459 	le->pathPos = le->pathLength = 0;
460 	if (le->stepList != nullptr) {
461 		leStep_t* step = le->stepList->next;
462 		Mem_Free(le->stepList);
463 		le->stepList = step;
464 		if (step != nullptr) {
465 			le->stepIndex--;
466 		} else if (le->stepIndex != 0) {
467 			Com_Error(ERR_DROP, "stepindex for entnum %i is out of sync (%i should be 0)\n", le->entnum, le->stepIndex);
468 		}
469 	}
470 
471 	/* keep this animation until something happens */
472 	LE_SetThink(le, nullptr);
473 }
474 
475 /**
476  * @brief Plays sound of content for moving actor.
477  * @param[in] le Pointer to local entity being an actor.
478  * @param[in] contents The contents flag of the brush we are currently in
479  * @note Currently it supports only CONTENTS_WATER, any other special contents
480  * can be added here anytime.
481  */
LE_PlaySoundFileForContents(le_t * le,int contents)482 static void LE_PlaySoundFileForContents (le_t* le, int contents)
483 {
484 	/* only play those water sounds when an actor jumps into the water - but not
485 	 * if he enters carefully in crouched mode */
486 	if (!LE_IsCrouched(le)) {
487 		if (contents & CONTENTS_WATER) {
488 			/* were we already in the water? */
489 			if (le->positionContents & CONTENTS_WATER) {
490 				/* play water moving sound */
491 				S_PlayStdSample(SOUND_WATER_MOVE, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_FOOTSTEPS);
492 			} else {
493 				/* play water entering sound */
494 				S_PlayStdSample(SOUND_WATER_IN, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_FOOTSTEPS);
495 			}
496 			return;
497 		}
498 
499 		if (le->positionContents & CONTENTS_WATER) {
500 			/* play water leaving sound */
501 			S_PlayStdSample(SOUND_WATER_OUT, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_FOOTSTEPS);
502 		}
503 	}
504 }
505 
506 /**
507  * @brief Plays step sounds and draw particles for different terrain types
508  * @param[in] le The local entity to play the sound and draw the particle for
509  * @param[in] textureName The name of the texture the actor is standing on
510  * @sa LET_PathMove
511  */
LE_PlaySoundFileAndParticleForSurface(le_t * le,const char * textureName)512 static void LE_PlaySoundFileAndParticleForSurface (le_t* le, const char* textureName)
513 {
514 	const terrainType_t* t;
515 	vec3_t origin;
516 
517 	t = Com_GetTerrainType(textureName);
518 	if (!t)
519 		return;
520 
521 	/* origin might not be up-to-date here - but pos should be */
522 	PosToVec(le->pos, origin);
523 
524 	/** @todo use the Grid_Fall method (ACTOR_SIZE_NORMAL) to ensure, that the particle is
525 	 * drawn at the ground (if needed - maybe the origin is already ground aligned)*/
526 	if (t->particle) {
527 		/* check whether actor is visible */
528 		if (!LE_IsStunned(le) && LE_IsLivingAndVisibleActor(le))
529 			CL_ParticleSpawn(t->particle, 0, origin);
530 	}
531 	if (t->footstepSound) {
532 		Com_DPrintf(DEBUG_SOUND, "LE_PlaySoundFileAndParticleForSurface: volume %.2f\n", t->footstepVolume);
533 		S_LoadAndPlaySample(t->footstepSound, origin, SOUND_ATTN_STATIC, t->footstepVolume);
534 	}
535 }
536 
537 /**
538  * sqrt(2) for diagonal movement
539  */
LE_ActorGetStepTime(const le_t * le,const pos3_t pos,const pos3_t oldPos,const int dir,const int speed)540 int LE_ActorGetStepTime (const le_t* le, const pos3_t pos, const pos3_t oldPos, const int dir, const int speed)
541 {
542 	if (dir != DIRECTION_FALL) {
543 		return (((dir & (CORE_DIRECTIONS - 1)) >= BASE_DIRECTIONS ? UNIT_SIZE * 1.41 : UNIT_SIZE) * 1000 / speed);
544 	} else {
545 		vec3_t start, dest;
546 		/* This needs to account for the distance of the fall. */
547 		Grid_PosToVec(cl.mapData->routing, le->fieldSize, oldPos, start);
548 		Grid_PosToVec(cl.mapData->routing, le->fieldSize, pos, dest);
549 		/* 1/1000th of a second per model unit in height change */
550 		return (start[2] - dest[2]);
551 	}
552 }
553 
LE_PlayFootStepSound(le_t * le)554 static void LE_PlayFootStepSound (le_t* le)
555 {
556 	if (Q_strvalid(le->teamDef->footstepSound)) {
557 		S_LoadAndPlaySample(le->teamDef->footstepSound, le->origin, SOUND_ATTN_NORM, SND_VOLUME_FOOTSTEPS);
558 		return;
559 	}
560 	/* walking in water will not play the normal footstep sounds */
561 	if (!le->pathContents[le->pathPos]) {
562 		vec3_t from, to;
563 
564 		/* prepare trace vectors */
565 		PosToVec(le->pos, from);
566 		VectorCopy(from, to);
567 		/* we should really hit the ground with this */
568 		to[2] -= UNIT_HEIGHT;
569 
570 		const trace_t trace = CL_Trace(from, to, AABB(), nullptr, nullptr, MASK_SOLID, cl_worldlevel->integer);
571 		if (trace.surface)
572 			LE_PlaySoundFileAndParticleForSurface(le, trace.surface->name);
573 	} else
574 		LE_PlaySoundFileForContents(le, le->pathContents[le->pathPos]);
575 }
576 
LE_DoPathMove(le_t * le)577 static void LE_DoPathMove (le_t* le)
578 {
579 	/* next part */
580 	const dvec_t dvec = le->dvtab[le->pathPos];
581 	const byte dir = getDVdir(dvec);
582 	const byte crouchingState = LE_IsCrouched(le) ? 1 : 0;
583 	/* newCrouchingState needs to be set to the current crouching state
584 	 * and is possibly updated by PosAddDV. */
585 	byte newCrouchingState = crouchingState;
586 	PosAddDV(le->pos, newCrouchingState, dvec);
587 
588 	LE_PlayFootStepSound(le);
589 
590 	/* only change the direction if the actor moves horizontally. */
591 	if (dir < CORE_DIRECTIONS || dir >= FLYING_DIRECTIONS)
592 		le->angle = dir & (CORE_DIRECTIONS - 1);
593 	le->angles[YAW] = directionAngles[le->angle];
594 	le->startTime = le->endTime;
595 	/* check for straight movement or diagonal movement */
596 	assert(le->speed[le->pathPos]);
597 	le->endTime += LE_ActorGetStepTime(le, le->pos, le->oldPos, dir, le->speed[le->pathPos]);
598 
599 	le->positionContents = le->pathContents[le->pathPos];
600 	le->pathPos++;
601 }
602 
603 /**
604  * @brief Ends the move of an actor
605  */
LE_DoEndPathMove(le_t * le)606 void LE_DoEndPathMove (le_t* le)
607 {
608 	/* Verify the current position */
609 	if (!VectorCompare(le->pos, le->newPos))
610 		Com_Error(ERR_DROP, "LE_DoEndPathMove: Actor movement is out of sync: %i:%i:%i should be %i:%i:%i (step %i of %i) (team %i)",
611 				le->pos[0], le->pos[1], le->pos[2], le->newPos[0], le->newPos[1], le->newPos[2], le->pathPos, le->pathLength, le->team);
612 
613 	CL_ActorConditionalMoveCalc(le);
614 	/* if the moving actor was not the selected one, */
615 	/* recalc the pathing table for the selected one, too. */
616 	if (!LE_IsSelected(le)) {
617 		CL_ActorConditionalMoveCalc(selActor);
618 	}
619 
620 	LE_LinkFloorContainer(le);
621 
622 	LE_SetThink(le, LET_StartIdle);
623 	LE_ExecuteThink(le);
624 	LE_Unlock(le);
625 }
626 
627 /**
628  * @brief Spawns particle effects for a hit actor.
629  * @param[in] le The actor to spawn the particles for.
630  * @param[in] impact The impact location (where the particles are spawned).
631  * @param[in] normal The index of the normal vector of the particles (think: impact angle).
632  */
LE_ActorBodyHit(const le_t * le,const vec3_t impact,int normal)633 static void LE_ActorBodyHit (const le_t* le, const vec3_t impact, int normal)
634 {
635 	if (le->teamDef) {
636 		/* Spawn "hit_particle" if defined in teamDef. */
637 		if (le->teamDef->hitParticle[0] != '\0')
638 			CL_ParticleSpawn(le->teamDef->hitParticle, 0, impact, bytedirs[normal]);
639 	}
640 }
641 
642 /**
643  * @brief Move the actor along the path to the given location
644  * @note Think function
645  * @sa CL_ActorDoMove
646  */
LET_PathMove(le_t * le)647 static void LET_PathMove (le_t* le)
648 {
649 	float frac;
650 	vec3_t start, dest, delta;
651 
652 	/* check for start of the next step */
653 	if (cl.time < le->startTime)
654 		return;
655 
656 	/* move ahead */
657 	while (cl.time >= le->endTime) {
658 		/* Ensure that we are displayed where we are supposed to be, in case the last frame came too quickly. */
659 		Grid_PosToVec(cl.mapData->routing, le->fieldSize, le->pos, le->origin);
660 
661 		/* Record the last position of movement calculations. */
662 		VectorCopy(le->pos, le->oldPos);
663 
664 		if (le->pathPos < le->pathLength) {
665 			LE_DoPathMove(le);
666 		} else {
667 			LE_DoEndPathMove(le);
668 			return;
669 		}
670 	}
671 
672 	/* interpolate the position */
673 	Grid_PosToVec(cl.mapData->routing, le->fieldSize, le->oldPos, start);
674 	Grid_PosToVec(cl.mapData->routing, le->fieldSize, le->pos, dest);
675 	VectorSubtract(dest, start, delta);
676 
677 	frac = (float) (cl.time - le->startTime) / (float) (le->endTime - le->startTime);
678 
679 	/* calculate the new interpolated actor origin in the world */
680 	VectorMA(start, frac, delta, le->origin);
681 }
682 
683 /**
684  * @note Think function
685  * @brief Change the actors animation to walking
686  * @note See the *.anm files in the models dir
687  */
LET_StartPathMove(le_t * le)688 void LET_StartPathMove (le_t* le)
689 {
690 	/* center view (if wanted) */
691 	if (!cls.isOurRound() && le->team != TEAM_CIVILIAN)
692 		LE_CenterView(le);
693 
694 	/* initial animation or animation change */
695 	R_AnimChange(&le->as, le->model1, LE_GetAnim("walk", le->right, le->left, le->state));
696 	if (!le->as.change)
697 		Com_Printf("LET_StartPathMove: Could not change anim of le: %i, team: %i, pnum: %i\n",
698 			le->entnum, le->team, le->pnum);
699 
700 	LE_SetThink(le, LET_PathMove);
701 	LE_ExecuteThink(le);
702 }
703 
704 /**
705  * @note Think function.
706  * @brief Handle move for invisible actors.
707  * @todo Is there something we should do here?
708  */
LET_HiddenMove(le_t * le)709 void LET_HiddenMove (le_t* le)
710 {
711 	VectorCopy(le->newPos, le->pos);
712 	LE_SetThink(le, LET_StartIdle);
713 	LE_ExecuteThink(le);
714 	LE_Unlock(le);
715 }
716 
717 /**
718  * @note Think function
719  */
LET_Projectile(le_t * le)720 static void LET_Projectile (le_t* le)
721 {
722 	if (cl.time >= le->endTime) {
723 		vec3_t impact;
724 		VectorCopy(le->origin, impact);
725 		CL_ParticleFree(le->ptl);
726 		/* don't run the think function again */
727 		le->inuse = false;
728 		if (Q_strvalid(le->ref1)) {
729 			VectorCopy(le->ptl->s, impact);
730 			le->ptl = CL_ParticleSpawn(le->ref1, 0, impact, bytedirs[le->angle]);
731 			VecToAngles(bytedirs[le->state], le->ptl->angles);
732 		}
733 		if (Q_strvalid(le->ref2)) {
734 			S_LoadAndPlaySample(le->ref2, impact, le->fd->impactAttenuation, SND_VOLUME_WEAPONS);
735 		}
736 		if (le->ref3) {
737 			/* Spawn blood particles (if defined) if actor(-body) was hit. Even if actor is dead. */
738 			/** @todo Special particles for stun attack (mind you that there is
739 			 * electrical and gas/chemical stunning)? */
740 			if (le->fd->obj->dmgtype != csi.damStunGas)
741 				LE_ActorBodyHit(le->ref3, impact, le->angle);
742 			CL_ActorPlaySound(le->ref3, SND_HURT);
743 		}
744 	} else if (CL_OutsideMap(le->ptl->s, UNIT_SIZE * 10)) {
745 		le->endTime = cl.time;
746 		CL_ParticleFree(le->ptl);
747 		/* don't run the think function again */
748 		le->inuse = false;
749 	}
750 }
751 
752 /*===========================================================================
753  LE Special Effects
754 =========================================================================== */
755 
LE_AddProjectile(const fireDef_t * fd,int flags,const vec3_t muzzle,const vec3_t impact,int normal,le_t * leVictim)756 void LE_AddProjectile (const fireDef_t* fd, int flags, const vec3_t muzzle, const vec3_t impact, int normal, le_t* leVictim)
757 {
758 	le_t* le;
759 	vec3_t delta;
760 	float dist;
761 
762 	/* add le */
763 	le = LE_Add(0);
764 	if (!le)
765 		return;
766 	LE_SetInvisible(le);
767 	/* bind particle */
768 	le->ptl = CL_ParticleSpawn(fd->projectile, 0, muzzle);
769 	if (!le->ptl) {
770 		le->inuse = false;
771 		return;
772 	}
773 
774 	/* calculate parameters */
775 	VectorSubtract(impact, muzzle, delta);
776 	dist = VectorLength(delta);
777 
778 	VecToAngles(delta, le->ptl->angles);
779 	/* direction - bytedirs index */
780 	le->angle = normal;
781 	le->fd = fd;
782 
783 	/* infinite speed projectile? */
784 	if (!fd->speed) {
785 		le->inuse = false;
786 		le->ptl->size[0] = dist;
787 		VectorMA(muzzle, 0.5, delta, le->ptl->s);
788 		if ((flags & (SF_IMPACT | SF_BODY)) || (fd->splrad && !fd->bounce)) {
789 			ptl_t* ptl = nullptr;
790 			const float* dir = bytedirs[le->angle];
791 			if (flags & SF_BODY) {
792 				if (fd->hitBodySound != nullptr) {
793 					S_LoadAndPlaySample(fd->hitBodySound, le->origin, le->fd->impactAttenuation, SND_VOLUME_WEAPONS);
794 				}
795 				if (fd->hitBody != nullptr)
796 					ptl = CL_ParticleSpawn(fd->hitBody, 0, impact, dir);
797 
798 				/* Spawn blood particles (if defined) if actor(-body) was hit. Even if actor is dead. */
799 				/** @todo Special particles for stun attack (mind you that there is
800 				 * electrical and gas/chemical stunning)? */
801 				if (leVictim) {
802 					if (fd->obj->dmgtype != csi.damStunGas)
803 						LE_ActorBodyHit(leVictim, impact, le->angle);
804 					CL_ActorPlaySound(leVictim, SND_HURT);
805 				}
806 			} else {
807 				if (fd->impactSound != nullptr) {
808 					S_LoadAndPlaySample(fd->impactSound, le->origin, le->fd->impactAttenuation, SND_VOLUME_WEAPONS);
809 				}
810 				if (fd->impact != nullptr)
811 					ptl = CL_ParticleSpawn(fd->impact, 0, impact, dir);
812 			}
813 			if (ptl)
814 				VecToAngles(dir, ptl->angles);
815 		}
816 		return;
817 	}
818 	/* particle properties */
819 	VectorScale(delta, fd->speed / dist, le->ptl->v);
820 	le->endTime = cl.time + 1000 * dist / fd->speed;
821 
822 	/* think function */
823 	if (flags & SF_BODY) {
824 		le->ref1 = fd->hitBody;
825 		le->ref2 = fd->hitBodySound;
826 		le->ref3 = leVictim;
827 	} else if ((flags & SF_IMPACT) || (fd->splrad && !fd->bounce)) {
828 		le->ref1 = fd->impact;
829 		le->ref2 = fd->impactSound;
830 	} else {
831 		le->ref1 = nullptr;
832 		if (flags & SF_BOUNCING)
833 			le->ref2 = fd->bounceSound;
834 	}
835 
836 	LE_SetThink(le, LET_Projectile);
837 	LE_ExecuteThink(le);
838 }
839 
840 /**
841  * @brief Returns the index of the biggest item in the inventory list
842  * @note This item is the only one that will be drawn when lying at the floor
843  * @sa LE_PlaceItem
844  * @return the item index in the @c csi.ods array
845  * @note Only call this for none empty Item
846  */
LE_BiggestItem(const Item * ic)847 static const objDef_t* LE_BiggestItem (const Item* ic)
848 {
849 	assert(ic);
850 	const objDef_t* max;
851 	int maxSize = 0;
852 
853 	for (max = ic->def(); ic; ic = ic->getNext()) {
854 		const int size = INVSH_ShapeSize(ic->def()->shape);
855 		if (size > maxSize) {
856 			max = ic->def();
857 			maxSize = size;
858 		}
859 	}
860 
861 	/* there must be an item in the Item */
862 	assert(max);
863 	return max;
864 }
865 
866 /**
867  * @sa CL_BiggestItem
868  * @param[in] le The local entity (ET_ITEM) with the floor container
869  */
LE_PlaceItem(le_t * le)870 void LE_PlaceItem (le_t* le)
871 {
872 	le_t* actor = nullptr;
873 
874 	assert(LE_IsItem(le));
875 
876 	/* search owners (there can be many, some of them dead) */
877 	while ((actor = LE_GetNextInUse(actor))) {
878 		if ((actor->type == ET_ACTOR || actor->type == ET_ACTOR2x2)
879 		 && VectorCompare(actor->pos, le->pos)) {
880 			if (le->getFloorContainer())
881 				actor->setFloor(le);
882 		}
883 	}
884 
885 	/* the le is an ET_ITEM entity, this entity is there to render dropped items
886 	 * if there are no items in the floor container, this entity can be
887 	 * deactivated */
888 	Item* floorCont = le->getFloorContainer();
889 	if (floorCont) {
890 		const objDef_t* biggest = LE_BiggestItem(floorCont);
891 		le->model1 = cls.modelPool[biggest->idx];
892 		if (!le->model1)
893 			Com_Error(ERR_DROP, "Model for item %s is not precached in the cls.model_weapons array",
894 				biggest->id);
895 		Grid_PosToVec(cl.mapData->routing, le->fieldSize, le->pos, le->origin);
896 		VectorSubtract(le->origin, biggest->center, le->origin);
897 		le->angles[ROLL] = 90;
898 		/*le->angles[YAW] = 10*(int)(le->origin[0] + le->origin[1] + le->origin[2]) % 360; */
899 		le->origin[2] -= GROUND_DELTA;
900 	} else {
901 		/* If no items in floor inventory, don't draw this le - the container is
902 		 * maybe empty because an actor picked up the last items here */
903 		le->flags |= LE_REMOVE_NEXT_FRAME;
904 	}
905 }
906 
907 /**
908  * @param[in] fd The grenade fire definition
909  * @param[in] flags bitmask: SF_BODY, SF_IMPACT, SF_BOUNCING, SF_BOUNCED
910  * @param[in] muzzle starting/location vector
911  * @param[in] v0 velocity vector
912  * @param[in] dt delta seconds
913  * @param[in] leVictim The actor the grenade is thrown at (not yet supported)
914  */
LE_AddGrenade(const fireDef_t * fd,int flags,const vec3_t muzzle,const vec3_t v0,int dt,le_t * leVictim)915 void LE_AddGrenade (const fireDef_t* fd, int flags, const vec3_t muzzle, const vec3_t v0, int dt, le_t* leVictim)
916 {
917 	le_t* le;
918 	vec3_t accel;
919 
920 	/* add le */
921 	le = LE_Add(0);
922 	if (!le)
923 		return;
924 	LE_SetInvisible(le);
925 
926 	/* bind particle */
927 	VectorSet(accel, 0, 0, -GRAVITY);
928 	le->ptl = CL_ParticleSpawn(fd->projectile, 0, muzzle, v0, accel);
929 	if (!le->ptl) {
930 		le->inuse = false;
931 		return;
932 	}
933 	/* particle properties */
934 	VectorSet(le->ptl->angles, 360 * crand(), 360 * crand(), 360 * crand());
935 	VectorSet(le->ptl->omega, 500 * crand(), 500 * crand(), 500 * crand());
936 
937 	/* think function */
938 	if (flags & SF_BODY) {
939 		le->ref1 = fd->hitBody;
940 		le->ref2 = fd->hitBodySound;
941 		le->ref3 = leVictim;
942 	} else if ((flags & SF_IMPACT) || (fd->splrad && !fd->bounce)) {
943 		le->ref1 = fd->impact;
944 		le->ref2 = fd->impactSound;
945 	} else {
946 		le->ref1 = nullptr;
947 		if (flags & SF_BOUNCING)
948 			le->ref2 = fd->bounceSound;
949 	}
950 
951 	le->endTime = cl.time + dt;
952 	/* direction - bytedirs index (0,0,1) */
953 	le->angle = 5;
954 	le->fd = fd;
955 	LE_SetThink(le, LET_Projectile);
956 	LE_ExecuteThink(le);
957 }
958 
959 /**
960  * @brief Add function for brush models
961  * @sa LE_AddToScene
962  */
LE_BrushModelAction(le_t * le,entity_t * ent)963 bool LE_BrushModelAction (le_t* le, entity_t* ent)
964 {
965 	switch (le->type) {
966 	case ET_ROTATING:
967 	case ET_DOOR:
968 		/* These cause the model to render correctly */
969 		le->aabb.set(ent->eBox);
970 		VectorCopy(ent->origin, le->origin);
971 		VectorCopy(ent->angles, le->angles);
972 		break;
973 	case ET_DOOR_SLIDING:
974 		VectorCopy(le->origin, ent->origin);
975 		break;
976 	case ET_BREAKABLE:
977 		break;
978 	case ET_TRIGGER_RESCUE: {
979 		float x, y, z, xmax;
980 		const int drawFlags = cl_map_draw_rescue_zone->integer;
981 
982 		if (!((1 << cl_worldlevel->integer) & le->levelflags))
983 			return false;
984 
985 		ent->flags = 0; /* Do not draw anything at all, if drawFlags set to 0 */
986 		enum { DRAW_TEXTURE = 0x1, DRAW_CIRCLES = 0x2 };
987 		ent->model = nullptr;
988 		ent->alpha = 0.3f;
989 		VectorSet(ent->color, 0.5f, 1.0f, 0.0f);
990 		if ((drawFlags & DRAW_TEXTURE) && ent->texture == nullptr) {
991 			ent->flags = RF_BOX;
992 			ent->texture = R_FindPics("sfx/misc/rescue");
993 			VectorSet(ent->color, 1, 1, 1);
994 		}
995 		ent->eBox.set(le->aabb);
996 
997 		if (!(drawFlags & DRAW_CIRCLES))
998 			return false;
999 
1000 		/* There should be an easier way than calculating the grid coords back from the world coords */
1001 		z = roundf(le->aabb.mins[2] / UNIT_HEIGHT) * UNIT_HEIGHT + UNIT_HEIGHT / 8.0f;
1002 		xmax = roundf(le->aabb.getMaxX() / UNIT_SIZE) * UNIT_SIZE - 0.1f;
1003 		for (x = roundf(le->aabb.getMinX() / UNIT_SIZE) * UNIT_SIZE; x < xmax; x += UNIT_SIZE) {
1004 			const float ymax = roundf(le->aabb.maxs[1] / UNIT_SIZE) * UNIT_SIZE - 0.1f;
1005 			for (y = roundf(le->aabb.mins[1] / UNIT_SIZE) * UNIT_SIZE; y < ymax; y += UNIT_SIZE) {
1006 				const vec3_t pos = {x + UNIT_SIZE / 4.0f, y + UNIT_SIZE / 4.0f, z};
1007 				entity_t circle(RF_PATH);
1008 				VectorCopy(pos, circle.origin);
1009 				circle.oldorigin[0] = circle.oldorigin[1] = circle.oldorigin[2] = UNIT_SIZE / 2.0f;
1010 				VectorCopy(ent->color, circle.color);
1011 				circle.alpha = ent->alpha;
1012 
1013 				R_AddEntity(&circle);
1014 			}
1015 		}
1016 
1017 		/* no other rendering entities should be added for the local entity */
1018 		return false;
1019 	}
1020 	default:
1021 		break;
1022 	}
1023 
1024 	return true;
1025 }
1026 
LET_BrushModel(le_t * le)1027 void LET_BrushModel (le_t* le)
1028 {
1029 	const int delay = cl.time - le->thinkDelay;
1030 
1031 	/* Updating model faster than 1000 times per second seems to be pretty much pointless */
1032 	if (delay < 1)
1033 		return;
1034 
1035 	if (le->type == ET_ROTATING) {
1036 		const float angle = le->angles[le->angle] + 0.001 * delay * le->rotationSpeed; /* delay is in msecs, speed in degrees per second */
1037 		le->angles[le->angle] = (angle >= 360.0 ? angle - 360.0 : angle);
1038 	}
1039 
1040 	le->thinkDelay = cl.time;
1041 }
1042 
LMT_Init(localModel_t * localModel)1043 void LMT_Init (localModel_t* localModel)
1044 {
1045 	if (localModel->target[0] != '\0') {
1046 		localModel->parent = LM_GetByID(localModel->target);
1047 		if (!localModel->parent)
1048 			Com_Error(ERR_DROP, "Could not find local model entity with the id: '%s'.", localModel->target);
1049 	}
1050 
1051 	/* no longer needed */
1052 	localModel->think = nullptr;
1053 }
1054 
1055 /**
1056  * @brief Rotates a door in the given speed
1057  *
1058  * @param[in] le The local entity of the door to rotate
1059  * @param[in] speed The speed to rotate the door with
1060  */
LET_RotateDoor(le_t * le,int speed)1061 void LET_RotateDoor (le_t* le, int speed)
1062 {
1063 	/** @todo lerp the rotation */
1064 	const int angle = speed > 0 ? DOOR_ROTATION_ANGLE : -DOOR_ROTATION_ANGLE;
1065 	if (le->dir & DOOR_OPEN_REVERSE)
1066 		le->angles[le->dir & 3] -= angle;
1067 	else
1068 		le->angles[le->dir & 3] += angle;
1069 
1070 	CM_SetInlineModelOrientation(cl.mapTiles, le->inlineModelName, le->origin, le->angles);
1071 	CL_RecalcRouting(le);
1072 
1073 	/* reset the think function as the movement finished */
1074 	LE_SetThink(le, nullptr);
1075 }
1076 
1077 /**
1078  * @brief Slides a door
1079  *
1080  * @note Though doors, sliding doors need a very different handling:
1081  * because it's movement is animated (unlike the rotating door),
1082  * the final position that is used to calculate the routing data
1083  * is set once the animation finished (because this recalculation
1084  * might be very expensive).
1085  *
1086  * @param[in,out] le The local entity of the inline model
1087  * @param[in] speed The speed to slide with - a negative value to close the door
1088  * @sa Door_SlidingUse
1089  */
LET_SlideDoor(le_t * le,int speed)1090 void LET_SlideDoor (le_t* le, int speed)
1091 {
1092 	vec3_t moveAngles, moveDir;
1093 	bool endPos = false;
1094 	int distance;
1095 
1096 	/* get the movement angle vector */
1097 	GET_SLIDING_DOOR_SHIFT_VECTOR(le->dir, speed, moveAngles);
1098 
1099 	/* this origin is only an offset to the absolute mins/maxs for rendering */
1100 	VectorAdd(le->origin, moveAngles, le->origin);
1101 
1102 	/* get the direction vector from the movement angles that were set on the entity */
1103 	AngleVectors(moveAngles, moveDir, nullptr, nullptr);
1104 	moveDir[0] = fabsf(moveDir[0]);
1105 	moveDir[1] = fabsf(moveDir[1]);
1106 	moveDir[2] = fabsf(moveDir[2]);
1107 	/* calculate the distance from the movement angles and the entity size */
1108 	distance = DotProduct(moveDir, le->size);
1109 
1110 	if (speed > 0) {
1111 		/* check whether the distance the door may slide is slided already
1112 		 * - if so, stop the movement of the door */
1113 		if (fabs(le->origin[le->dir & 3]) >= distance)
1114 			endPos = true;
1115 	} else {
1116 		/* the sliding door has not origin set - except when it is opened. This door type is no
1117 		 * origin brush based bmodel entity. So whenever the origin vector is not the zero vector,
1118 		 * the door is opened. */
1119 		if (VectorEmpty(le->origin))
1120 			endPos = true;
1121 	}
1122 
1123 	if (endPos) {
1124 		vec3_t distanceVec;
1125 		/* the door finished its move - either close or open, so make sure to recalc the routing
1126 		 * data and set the mins/maxs for the inline brush model */
1127 		cBspModel_t* model = CM_InlineModel(cl.mapTiles, le->inlineModelName);
1128 
1129 		assert(model);
1130 
1131 		/* we need the angles vector normalized */
1132 		GET_SLIDING_DOOR_SHIFT_VECTOR(le->dir, (speed < 0) ? -1 : 1, moveAngles);
1133 
1134 		/* the bounding box of the door is updated in one step - here is no lerping needed */
1135 		VectorMul(distance, moveAngles, distanceVec);
1136 
1137 		VectorAdd(model->mins, distanceVec, model->mins);
1138 		VectorAdd(model->maxs, distanceVec, model->maxs);
1139 		CL_RecalcRouting(le);
1140 
1141 		/* reset the think function as the movement finished */
1142 		LE_SetThink(le, nullptr);
1143 	} else
1144 		le->thinkDelay = 1000;
1145 }
1146 
1147 /**
1148  * @brief Adds ambient sounds from misc_sound entities
1149  * @sa CL_SpawnParseEntitystring
1150  */
LE_AddAmbientSound(const char * sound,const vec3_t origin,int levelflags,float volume,float attenuation)1151 void LE_AddAmbientSound (const char* sound, const vec3_t origin, int levelflags, float volume, float attenuation)
1152 {
1153 	le_t* le;
1154 	int sampleIdx;
1155 
1156 	if (strstr(sound, "sound/"))
1157 		sound += 6;
1158 
1159 	sampleIdx = S_LoadSampleIdx(sound);
1160 	if (!sampleIdx)
1161 		return;
1162 
1163 	le = LE_Add(0);
1164 	if (!le) {
1165 		Com_Printf("Could not add ambient sound entity\n");
1166 		return;
1167 	}
1168 	le->type = ET_SOUND;
1169 	le->sampleIdx = sampleIdx;
1170 	VectorCopy(origin, le->origin);
1171 	LE_SetInvisible(le);
1172 	le->levelflags = levelflags;
1173 	le->attenuation = attenuation;
1174 
1175 	if (volume < 0.0f || volume > 1.0f) {
1176 		le->volume = SND_VOLUME_DEFAULT;
1177 		Com_Printf("Invalid volume for local entity given - only values between 0.0 and 1.0 are valid\n");
1178 	} else {
1179 		le->volume = volume;
1180 	}
1181 
1182 	Com_DPrintf(DEBUG_SOUND, "Add ambient sound '%s' with volume %f\n", sound, volume);
1183 }
1184 
1185 /*===========================================================================
1186  LE Management functions
1187 =========================================================================== */
1188 
1189 /**
1190  * @brief Add a new local entity to the scene
1191  * @param[in] entnum The entity number (server side)
1192  * @sa LE_Get
1193  */
LE_Add(int entnum)1194 le_t* LE_Add (int entnum)
1195 {
1196 	le_t* le = nullptr;
1197 
1198 	while ((le = LE_GetNext(le))) {
1199 		if (!le->inuse)
1200 			/* found a free LE */
1201 			break;
1202 	}
1203 
1204 	/* list full, try to make list longer */
1205 	if (!le) {
1206 		if (cl.numLEs >= MAX_EDICTS) {
1207 			/* no free LEs */
1208 			Com_Error(ERR_DROP, "Too many LEs");
1209 		}
1210 
1211 		/* list isn't too long */
1212 		le = &cl.LEs[cl.numLEs];
1213 		cl.numLEs++;
1214 	}
1215 
1216 	/* initialize the new LE */
1217 	OBJZERO(*le);
1218 	le->inuse = true;
1219 	le->entnum = entnum;
1220 	le->fieldSize = ACTOR_SIZE_NORMAL;
1221 	return le;
1222 }
1223 
_LE_NotFoundError(int entnum,int type,const char * file,const int line)1224 void _LE_NotFoundError (int entnum, int type, const char* file, const int line)
1225 {
1226 	Cmd_ExecuteString("debug_listle");
1227 	Cmd_ExecuteString("debug_listedicts");
1228 	if (type >= 0) {
1229 		Com_Error(ERR_DROP, "LE_NotFoundError: Could not get LE with entnum %i of type: %i (%s:%i)\n", entnum, type, file, line);
1230 	} else {
1231 		Com_Error(ERR_DROP, "LE_NotFoundError: Could not get LE with entnum %i (%s:%i)\n", entnum, file, line);
1232 	}
1233 }
1234 
1235 /**
1236  * @brief Center the camera on the local entity's origin
1237  * @param le The local entity which origin is used to center the camera
1238  * @sa CL_CenterView
1239  * @sa CL_ViewCenterAtGridPosition
1240  * @sa CL_CameraRoute
1241  */
LE_CenterView(const le_t * le)1242 void LE_CenterView (const le_t* le)
1243 {
1244 	if (!cl_centerview->integer)
1245 		return;
1246 
1247 	assert(le);
1248 	if (le->team == cls.team) {
1249 		const float minDistToMove = 4.0f * UNIT_SIZE;
1250 		const float dist = Vector2Dist(cl.cam.origin, le->origin);
1251 		if (dist < minDistToMove) {
1252 			pos3_t currentCamPos;
1253 			VecToPos(cl.cam.origin, currentCamPos);
1254 			if (le->pos[2] != currentCamPos[2])
1255 				Cvar_SetValue("cl_worldlevel", le->pos[2]);
1256 			return;
1257 		}
1258 
1259 		VectorCopy(le->origin, cl.cam.origin);
1260 	} else {
1261 		pos3_t pos;
1262 		VecToPos(cl.cam.origin, pos);
1263 		CL_CheckCameraRoute(pos, le->pos);
1264 	}
1265 }
1266 
1267 /**
1268  * @brief Searches all local entities for the one with the searched entnum
1269  * @param[in] entnum The entity number (server side)
1270  * @sa LE_Add
1271  */
LE_Get(int entnum)1272 le_t* LE_Get (int entnum)
1273 {
1274 	le_t* le = nullptr;
1275 
1276 	if (entnum == SKIP_LOCAL_ENTITY)
1277 		return nullptr;
1278 
1279 	while ((le = LE_GetNextInUse(le))) {
1280 		if (le->entnum == entnum)
1281 			/* found the LE */
1282 			return le;
1283 	}
1284 
1285 	/* didn't find it */
1286 	return nullptr;
1287 }
1288 
1289 /**
1290  * @brief Checks if a given le_t structure is locked, i.e., used by another event at this time.
1291  * @param entnum the entnum of the le_t struct involved.
1292  * @return true if the le_t is locked (used by another event), false if it's not or if it doesn't exist.
1293  */
LE_IsLocked(int entnum)1294 bool LE_IsLocked (int entnum)
1295 {
1296 	le_t* le = LE_Get(entnum);
1297 	return (le != nullptr && (le->flags & LE_LOCKED));
1298 }
1299 
1300 /**
1301  * @brief Markes a le_t struct as locked.  Should be called at the
1302  *  beginning of an event handler on this le_t, and paired with a LE_Unlock at the end.
1303  * @param le The struct to be locked.
1304  * @note Always make sure you call LE_Unlock at the end of the event
1305  *  (might be in a different function), to allow other events on this le_t.
1306  */
LE_Lock(le_t * le)1307 void LE_Lock (le_t* le)
1308 {
1309 	if (le->flags & LE_LOCKED)
1310 		Com_Error(ERR_DROP, "LE_Lock: Trying to lock %i which is already locked\n", le->entnum);
1311 
1312 	le->flags |= LE_LOCKED;
1313 }
1314 
1315 /**
1316  * @brief Unlocks a previously locked le_t struct.
1317  * @param le The le_t to unlock.
1318  * @note Make sure that this is always paired with the corresponding
1319  *  LE_Lock around the conceptual beginning and ending of a le_t event.
1320  *  Should never be called by the handler(s) of a different event than
1321  *  the one that locked le.  The owner of the lock is currently not
1322  *  checked.
1323  * @todo If the event loop ever becomes multithreaded, this should
1324  *  be a real mutex lock.
1325  */
LE_Unlock(le_t * le)1326 void LE_Unlock (le_t* le)
1327 {
1328 	if (!(le->flags & LE_LOCKED))
1329 		Com_Error(ERR_DROP, "LE_Unlock: Trying to unlock %i which is already unlocked\n", le->entnum);
1330 
1331 	le->flags &= ~LE_LOCKED;
1332 }
1333 
1334 /**
1335  * @brief Searches a local entity on a given grid field
1336  * @param[in] pos The grid pos to search for an item of the given type
1337  */
LE_GetFromPos(const pos3_t pos)1338 le_t* LE_GetFromPos (const pos3_t pos)
1339 {
1340 	le_t* le = nullptr;
1341 
1342 	while ((le = LE_GetNextInUse(le))) {
1343 		if (VectorCompare(le->pos, pos))
1344 			return le;
1345 	}
1346 
1347 	/* didn't find it */
1348 	return nullptr;
1349 }
1350 
1351 /**
1352  * @brief Iterate through the list of entities
1353  * @param lastLE The entity found in the previous iteration; if nullptr, we start at the beginning
1354  */
LE_GetNext(le_t * lastLE)1355 le_t* LE_GetNext (le_t* lastLE)
1356 {
1357 	le_t* endOfLEs = &cl.LEs[cl.numLEs];
1358 	le_t* le;
1359 
1360 	if (!cl.numLEs)
1361 		return nullptr;
1362 
1363 	if (!lastLE)
1364 		return cl.LEs;
1365 
1366 	assert(lastLE >= cl.LEs);
1367 	assert(lastLE < endOfLEs);
1368 
1369 	le = lastLE;
1370 
1371 	le++;
1372 	if (le >= endOfLEs)
1373 		return nullptr;
1374 	else
1375 		return le;
1376 }
1377 
1378 /**
1379  * @brief Iterate through the entities that are in use
1380  * @note we can hopefully get rid of this function once we know when it makes sense
1381  * to iterate through entities that are NOT in use
1382  * @param lastLE The entity found in the previous iteration; if nullptr, we start at the beginning
1383  */
LE_GetNextInUse(le_t * lastLE)1384 le_t* LE_GetNextInUse (le_t* lastLE)
1385 {
1386 	le_t* le = lastLE;
1387 
1388 	while ((le = LE_GetNext(le))) {
1389 		if (le->inuse)
1390 			break;
1391 	}
1392 	return le;
1393 }
1394 
1395 /**
1396  * @brief Returns entities that have origins within a spherical area.
1397  * @param[in] from The entity to start the search from. @c nullptr will start from the beginning.
1398  * @param[in] org The origin that is the center of the circle.
1399  * @param[in] rad radius to search an edict in.
1400  * @param[in] type Type of local entity. @c ET_NULL to ignore the type.
1401  */
LE_FindRadius(le_t * from,const vec3_t org,float rad,entity_type_t type)1402 le_t* LE_FindRadius (le_t* from, const vec3_t org, float rad, entity_type_t type)
1403 {
1404 	le_t* le = from;
1405 
1406 	while ((le = LE_GetNextInUse(le))) {
1407 		int j;
1408 		vec3_t eorg;
1409 		for (j = 0; j < 3; j++)
1410 			eorg[j] = org[j] - (le->origin[j] + (le->aabb.mins[j] + le->aabb.maxs[j]) * 0.5);
1411 		if (VectorLength(eorg) > rad)
1412 			continue;
1413 		if (type != ET_NULL && le->type != type)
1414 			continue;
1415 		return le;
1416 	}
1417 
1418 	return nullptr;
1419 }
1420 
1421 /**
1422  * @brief Searches a local entity on a given grid field
1423  * @param[in] type Entity type
1424  * @param[in] pos The grid pos to search for an item of the given type
1425  */
LE_Find(entity_type_t type,const pos3_t pos)1426 le_t* LE_Find (entity_type_t type, const pos3_t pos)
1427 {
1428 	le_t* le = nullptr;
1429 
1430 	while ((le = LE_GetNextInUse(le))) {
1431 		if (le->type == type && VectorCompare(le->pos, pos))
1432 			/* found the LE */
1433 			return le;
1434 	}
1435 
1436 	/* didn't find it */
1437 	return nullptr;
1438 }
1439 
1440 /** @sa BoxOffset in cl_actor.c */
1441 #define ModelOffset(i, target) (target[0]=(i-1)*(UNIT_SIZE+BOX_DELTA_WIDTH)/2, target[1]=(i-1)*(UNIT_SIZE+BOX_DELTA_LENGTH)/2, target[2]=0)
1442 
1443 /**
1444  * Origin brush entities are bmodel entities that have their mins/maxs relative to the world origin.
1445  * The origin vector of the entity will be used to calculate e.g. the culling (and not the mins/maxs like
1446  * for other entities).
1447  * @param le The local entity to check
1448  * @return @c true if the given local entity is a func_door or func_rotating
1449  */
LE_IsOriginBrush(const le_t * const le)1450 static inline bool LE_IsOriginBrush (const le_t* const le)
1451 {
1452 	return (le->type == ET_DOOR || le->type == ET_ROTATING);
1453 }
1454 
1455 /**
1456  * @brief Adds a box that highlights the current active door
1457  */
LE_AddEdictHighlight(const le_t * le)1458 static void LE_AddEdictHighlight (const le_t* le)
1459 {
1460 	const cBspModel_t* model = LE_GetClipModel(le);
1461 
1462 	entity_t ent(RF_BOX);
1463 	VectorSet(ent.color, 1, 1, 1);
1464 	ent.alpha = (sin(cl.time * 6.28) + 1.0) / 2.0;
1465 	CalculateMinsMaxs(le->angles, model->mins, model->maxs, le->origin, ent.eBox.mins, ent.eBox.maxs);
1466 	R_AddEntity(&ent);
1467 }
1468 
1469 /**
1470  * @sa CL_ViewRender
1471  * @sa CL_AddUGV
1472  * @sa CL_AddActor
1473  */
LE_AddToScene(void)1474 void LE_AddToScene (void)
1475 {
1476 	le_t* le;
1477 	int i;
1478 
1479 	for (i = 0, le = cl.LEs; i < cl.numLEs; i++, le++) {
1480 		if (le->flags & LE_REMOVE_NEXT_FRAME) {
1481 			le->inuse = false;
1482 			le->flags &= ~LE_REMOVE_NEXT_FRAME;
1483 		}
1484 		if (le->inuse && !LE_IsInvisible(le)) {
1485 			if (le->flags & LE_CHECK_LEVELFLAGS) {
1486 				if (!((1 << cl_worldlevel->integer) & le->levelflags))
1487 					continue;
1488 			} else if (le->flags & LE_ALWAYS_VISIBLE) {
1489 				/* show them always */
1490 			} else if (le->pos[2] > cl_worldlevel->integer)
1491 				continue;
1492 
1493 			entity_t ent(RF_NONE);
1494 			ent.alpha = le->alpha;
1495 
1496 			VectorCopy(le->angles, ent.angles);
1497 			ent.model = le->model1;
1498 			ent.skinnum = le->bodySkin;
1499 			ent.lighting = &le->lighting;
1500 
1501 			switch (le->contents) {
1502 			/* Only breakables do not use their origin; func_doors and func_rotating do!!!
1503 			 * But none of them have animations. */
1504 			case CONTENTS_SOLID:
1505 			case CONTENTS_DETAIL: /* they use mins/maxs */
1506 				break;
1507 			default:
1508 				/* set entity values */
1509 				R_EntitySetOrigin(&ent, le->origin);
1510 				VectorCopy(le->origin, ent.oldorigin);
1511 				/* store animation values */
1512 				ent.as = le->as;
1513 				break;
1514 			}
1515 
1516 			if (LE_IsOriginBrush(le)) {
1517 				ent.isOriginBrushModel = true;
1518 				R_EntitySetOrigin(&ent, le->origin);
1519 				VectorCopy(le->origin, ent.oldorigin);
1520 			}
1521 
1522 			/* Offset the model to be inside the cursor box */
1523 			switch (le->fieldSize) {
1524 			case ACTOR_SIZE_NORMAL:
1525 			case ACTOR_SIZE_2x2:
1526 				vec3_t modelOffset;
1527 				ModelOffset(le->fieldSize, modelOffset);
1528 				R_EntityAddToOrigin(&ent, modelOffset);
1529 				VectorAdd(ent.oldorigin, modelOffset, ent.oldorigin);
1530 				break;
1531 			default:
1532 				break;
1533 			}
1534 
1535 			if (LE_IsSelected(le) && le->clientAction != nullptr) {
1536 				const le_t* action = le->clientAction;
1537 				if (action->inuse && action->type > ET_NULL && action->type < ET_MAX)
1538 					LE_AddEdictHighlight(action);
1539 			}
1540 
1541 			/* call add function */
1542 			/* if it returns false, don't draw */
1543 			if (le->addFunc)
1544 				if (!le->addFunc(le, &ent))
1545 					continue;
1546 
1547 			/* add it to the scene */
1548 			R_AddEntity(&ent);
1549 
1550 			if (cl_le_debug->integer)
1551 				CL_ParticleSpawn("cross", 0, le->origin);
1552 		}
1553 	}
1554 }
1555 
1556 /**
1557  * @brief Cleanup unused LE inventories that the server sent to the client
1558  * also free some unused LE memory
1559  */
LE_Cleanup(void)1560 void LE_Cleanup (void)
1561 {
1562 	int i;
1563 	le_t* le;
1564 
1565 	Com_DPrintf(DEBUG_CLIENT, "LE_Cleanup: Clearing up to %i unused LE inventories\n", cl.numLEs);
1566 	for (i = cl.numLEs - 1, le = &cl.LEs[cl.numLEs - 1]; i >= 0; i--, le--) {
1567 		if (!le->inuse)
1568 			continue;
1569 		if (LE_IsActor(le))
1570 			CL_ActorCleanup(le);
1571 		else if (LE_IsItem(le))
1572 			cls.i.emptyContainer(&le->inv, CID_FLOOR);
1573 
1574 		le->inuse = false;
1575 	}
1576 }
1577 
1578 #ifdef DEBUG
1579 /**
1580  * @brief Shows a list of current know local entities with type and status
1581  */
LE_List_f(void)1582 void LE_List_f (void)
1583 {
1584 	int i;
1585 	le_t* le;
1586 
1587 	Com_Printf("number | entnum | type | inuse | invis | pnum | team | size |  HP | state | level | model/ptl\n");
1588 	for (i = 0, le = cl.LEs; i < cl.numLEs; i++, le++) {
1589 		Com_Printf("#%5i | #%5i | %4i | %5i | %5i | %4i | %4i | %4i | %3i | %5i | %5i | ",
1590 			i, le->entnum, le->type, le->inuse, LE_IsInvisible(le), le->pnum, le->team,
1591 			le->fieldSize, le->HP, le->state, le->levelflags);
1592 		if (le->type == ET_PARTICLE) {
1593 			if (le->ptl)
1594 				Com_Printf("%s\n", le->ptl->ctrl->name);
1595 			else
1596 				Com_Printf("no ptl\n");
1597 		} else if (le->model1)
1598 			Com_Printf("%s\n", le->model1->name);
1599 		else
1600 			Com_Printf("no mdl\n");
1601 	}
1602 }
1603 
1604 /**
1605  * @brief Shows a list of current know local models
1606  */
LM_List_f(void)1607 void LM_List_f (void)
1608 {
1609 	int i;
1610 	localModel_t* lm;
1611 
1612 	Com_Printf("number | entnum | skin | frame | lvlflg | renderflags | origin          | name\n");
1613 	for (i = 0, lm = cl.LMs; i < cl.numLMs; i++, lm++) {
1614 		Com_Printf("#%5i | #%5i | #%3i | #%4i | %6i | %11i | %5.0f:%5.0f:%3.0f | %s\n",
1615 			i, lm->entnum, lm->skin, lm->frame, lm->levelflags, lm->renderFlags,
1616 			lm->origin[0], lm->origin[1], lm->origin[2], lm->name);
1617 	}
1618 }
1619 
1620 #endif
1621 
1622 /*===========================================================================
1623  LE Tracing
1624 =========================================================================== */
1625 
1626 /** @brief Client side moveclip */
1627 typedef struct moveclip_s {
1628 	vec3_t boxmins, boxmaxs;	/**< enclose the test object along entire move */
1629 	const float* mins, *maxs;	/**< size of the moving object */
1630 	const float* start, *end;
1631 	trace_t trace;
1632 	const le_t* passle, *passle2;		/**< ignore these for clipping */
1633 	int contentmask;			/**< search these in your trace - see MASK_* */
1634 } moveclip_t;
1635 
LE_GetClipModel(const le_t * le)1636 const cBspModel_t* LE_GetClipModel (const le_t* le)
1637 {
1638 	const cBspModel_t* model;
1639 	const unsigned int index = le->modelnum1;
1640 	if (index > lengthof(cl.model_clip))
1641 		Com_Error(ERR_DROP, "Clip model index out of bounds");
1642 	model = cl.model_clip[index];
1643 	if (!model)
1644 		Com_Error(ERR_DROP, "LE_GetClipModel: Could not find inline model %u", index);
1645 	return model;
1646 }
1647 
LE_GetDrawModel(unsigned int index)1648 model_t* LE_GetDrawModel (unsigned int index)
1649 {
1650 	model_t* model;
1651 	if (index == 0 || index > lengthof(cl.model_draw))
1652 		Com_Error(ERR_DROP, "Draw model index out of bounds");
1653 	model = cl.model_draw[index];
1654 	if (!model)
1655 		Com_Error(ERR_DROP, "LE_GetDrawModel: Could not find model %u", index);
1656 	return model;
1657 }
1658 
1659 /**
1660  * @brief Returns a headnode that can be used for testing or clipping an
1661  * object of mins/maxs size.
1662  * Offset is filled in to contain the adjustment that must be added to the
1663  * testing object's origin to get a point to use with the returned hull.
1664  * @param[in] le The local entity to get the bmodel from
1665  * @param[out] tile The maptile the bmodel belongs, too
1666  * @param[out] rmaShift the shift vector in case of an RMA (needed for doors)
1667  * @param[out] angles The rotation of the entity (in case of bmodels)
1668  * @return The headnode for the local entity
1669  * @sa SV_HullForEntity
1670  */
CL_HullForEntity(const le_t * le,int * tile,vec3_t rmaShift,vec3_t angles)1671 static int32_t CL_HullForEntity (const le_t* le, int* tile, vec3_t rmaShift, vec3_t angles)
1672 {
1673 	/* special case for bmodels */
1674 	if (le->contents & CONTENTS_SOLID) {
1675 		const cBspModel_t* model = LE_GetClipModel(le);
1676 		/* special value for bmodel */
1677 		if (!model)
1678 			Com_Error(ERR_DROP, "CL_HullForEntity: Error - le with nullptr bmodel (%i)\n", le->type);
1679 		*tile = model->tile;
1680 		VectorCopy(le->angles, angles);
1681 		VectorCopy(model->shift, rmaShift);
1682 		return model->headnode;
1683 	} else {
1684 		/* might intersect, so do an exact clip */
1685 		*tile = 0;
1686 		VectorCopy(vec3_origin, angles);
1687 		VectorCopy(vec3_origin, rmaShift);
1688 		return CM_HeadnodeForBox(cl.mapTiles->mapTiles[*tile], le->aabb.mins, le->aabb.maxs);
1689 	}
1690 }
1691 
1692 /**
1693  * @brief Clip against solid entities
1694  * @sa CL_Trace
1695  * @sa SV_ClipMoveToEntities
1696  */
CL_ClipMoveToLEs(moveclip_t * clip)1697 static void CL_ClipMoveToLEs (moveclip_t* clip)
1698 {
1699 	le_t* le = nullptr;
1700 
1701 	if (clip->trace.allsolid)
1702 		return;
1703 
1704 	while ((le = LE_GetNextInUse(le))) {
1705 		int tile = 0;
1706 		int32_t headnode;
1707 		vec3_t angles;
1708 		vec3_t origin, shift;
1709 
1710 		if (!(le->contents & clip->contentmask))
1711 			continue;
1712 		if (le == clip->passle || le == clip->passle2)
1713 			continue;
1714 
1715 		headnode = CL_HullForEntity(le, &tile, shift, angles);
1716 		assert(headnode < MAX_MAP_NODES);
1717 
1718 		VectorCopy(le->origin, origin);
1719 
1720 		trace_t trace = CM_HintedTransformedBoxTrace(cl.mapTiles->mapTiles[tile], clip->start, clip->end, AABB(clip->mins, clip->maxs),
1721 				headnode, clip->contentmask, 0, origin, angles, shift, 1.0);
1722 
1723 		if (trace.fraction < clip->trace.fraction) {
1724 			bool oldStart;
1725 
1726 			/* make sure we keep a startsolid from a previous trace */
1727 			oldStart = clip->trace.startsolid;
1728 			trace.le = le;
1729 			clip->trace = trace;
1730 			clip->trace.startsolid |= oldStart;
1731 		/* if true, plane is not valid */
1732 		} else if (trace.allsolid) {
1733 			trace.le = le;
1734 			clip->trace = trace;
1735 		/* if true, the initial point was in a solid area */
1736 		} else if (trace.startsolid) {
1737 			trace.le = le;
1738 			clip->trace.startsolid = true;
1739 		}
1740 	}
1741 }
1742 
1743 
1744 /**
1745  * @brief Create the bounding box for the entire move
1746  * @param[in] start Start vector to start the trace from
1747  * @param[in] mins Bounding box used for tracing
1748  * @param[in] maxs Bounding box used for tracing
1749  * @param[in] end End vector to stop the trace at
1750  * @param[out] boxmins The resulting box mins
1751  * @param[out] boxmaxs The resulting box maxs
1752  * @sa CL_Trace
1753  * @note Box is expanded by 1
1754  */
CL_TraceBounds(const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,vec3_t boxmins,vec3_t boxmaxs)1755 static inline void CL_TraceBounds (const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs)
1756 {
1757 	int i;
1758 
1759 	for (i = 0; i < 3; i++) {
1760 		if (end[i] > start[i]) {
1761 			boxmins[i] = start[i] + mins[i] - 1;
1762 			boxmaxs[i] = end[i] + maxs[i] + 1;
1763 		} else {
1764 			boxmins[i] = end[i] + mins[i] - 1;
1765 			boxmaxs[i] = start[i] + maxs[i] + 1;
1766 		}
1767 	}
1768 }
1769 
1770 /**
1771  * @brief Moves the given mins/maxs volume through the world from start to end.
1772  * @note Passedict and edicts owned by passedict are explicitly not checked.
1773  * @sa CL_TraceBounds
1774  * @sa CL_ClipMoveToLEs
1775  * @sa SV_Trace
1776  * @param[in] start Start vector to start the trace from
1777  * @param[in] end End vector to stop the trace at
1778  * @param[in] box Bounding box used for tracing
1779  * @param[in] passle Ignore this local entity in the trace (might be nullptr)
1780  * @param[in] passle2 Ignore this local entity in the trace (might be nullptr)
1781  * @param[in] contentmask Searched content the trace should watch for
1782  * @param[in] worldLevel The worldlevel (0-7) to calculate the levelmask for the trace from
1783  */
CL_Trace(const vec3_t start,const vec3_t end,const AABB & box,const le_t * passle,le_t * passle2,int contentmask,int worldLevel)1784 trace_t CL_Trace (const vec3_t start, const vec3_t end, const AABB &box, const le_t* passle, le_t* passle2, int contentmask, int worldLevel)
1785 {
1786 	moveclip_t clip;
1787 
1788 	if (cl_trace_debug->integer)
1789 		R_DrawBoundingBoxBatched(box.mins, box.maxs);
1790 
1791 	/* clip to world */
1792 	clip.trace = CM_CompleteBoxTrace(cl.mapTiles, start, end, box, (1 << (worldLevel + 1)) - 1, contentmask, 0);
1793 	clip.trace.le = nullptr;
1794 	if (clip.trace.fraction == 0)
1795 		return clip.trace;		/* blocked by the world */
1796 
1797 	clip.contentmask = contentmask;
1798 	clip.start = start;
1799 	clip.end = end;
1800 	clip.mins = box.mins;
1801 	clip.maxs = box.maxs;
1802 	clip.passle = passle;
1803 	clip.passle2 = passle2;
1804 
1805 	/* create the bounding box of the entire move */
1806 	CL_TraceBounds(start, box.mins, box.maxs, end, clip.boxmins, clip.boxmaxs);
1807 
1808 	/* clip to other solid entities */
1809 	CL_ClipMoveToLEs(&clip);
1810 
1811 	return clip.trace;
1812 }
1813