1 /**
2  * @file
3  * @brief Actor related routines.
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_actor.h"
28 #include "../cgame/cl_game.h"
29 #include "cl_hud.h"
30 #include "cl_parse.h"
31 #include "cl_particle.h"
32 #include "cl_view.h"
33 #include "../cl_screen.h"
34 #include "../ui/ui_main.h"
35 #include "../ui/ui_popup.h"
36 #include "../ui/node/ui_node_container.h"
37 #include "../renderer/r_entity.h"
38 #include "../renderer/r_mesh.h"
39 #include "../../common/routing.h"
40 #include "../../common/grid.h"
41 
42 /** @brief Confirm actions in tactical mode - valid values are 0, 1 and 2 */
43 static cvar_t* confirm_actions;
44 /** @brief Player preference: should the server make guys stand for long walks, to save TU. */
45 static cvar_t* cl_autostand;
46 static cvar_t* cl_showactors;
47 
48 /* public */
49 le_t* selActor;
50 pos3_t truePos; /**< The cell at the current worldlevel under the mouse cursor. */
51 pos3_t mousePos; /**< The cell that an actor will move to when directed to move. */
52 static vec3_t mouseDraggingPos; /**< The world pos, which we "grab" to scroll the world in touchscreen mode. */
53 
54 /**
55  * @brief If you want to change the z level of targeting and shooting,
56  * use this value. Negative and positive offsets are possible
57  * @sa CL_ActorTargetAlign_f
58  * @sa G_ClientShoot
59  * @sa G_ShootGrenade
60  * @sa G_ShootSingle
61  */
62 static int mousePosTargettingAlign = 0;
63 
64 static le_t* mouseActor;
65 static le_t* interactEntity;
66 static pos3_t mouseLastPos;
67 
68 /**
69  * @brief Writes player action with its data.
70  * @param[in] playerAction Type of action.
71  * @param[in] entnum The server side edict number of the actor
72  */
MSG_Write_PA(player_action_t playerAction,int entnum,...)73 void MSG_Write_PA (player_action_t playerAction, int entnum, ...)
74 {
75 	va_list ap;
76 	dbuffer msg;
77 
78 	va_start(ap, entnum);
79 	NET_WriteFormat(&msg, "bbs", clc_action, playerAction, entnum);
80 	NET_vWriteFormat(&msg, pa_format[playerAction], ap);
81 	va_end(ap);
82 	NET_WriteMsg(cls.netStream, msg);
83 }
84 
85 /*
86 ==============================================================
87 ACTOR MENU UPDATING
88 ==============================================================
89 */
90 
CL_ActorSetFireDef(le_t * actor,const fireDef_t * fd)91 void CL_ActorSetFireDef (le_t* actor, const fireDef_t* fd)
92 {
93 	if (actor->fd != fd)
94 		mousePosTargettingAlign = 0;
95 	actor->fd = fd;
96 }
97 
98 /**
99  * @brief Decide how the actor will walk, taking into account autostanding.
100  * @param[in] le Pointer to an actor for which we set the moving mode.
101  */
CL_ActorMoveMode(const le_t * le)102 int CL_ActorMoveMode (const le_t* le)
103 {
104 	assert(le);
105 	if (!LE_IsCrouched(le))
106 		return WALKTYPE_WALKING;
107 
108 	/* Is the player using autostand? */
109 	if (!cl_autostand->integer)
110 		return WALKTYPE_CROUCH_WALKING;
111 
112 	/* ...and if this is a long walk... */
113 	if (RT_CanActorStandHere(cl.mapData->routing, le->fieldSize, le->pos)
114 		&& Grid_ShouldUseAutostand(&cl.pathMap, mousePos))
115 		return WALKTYPE_AUTOSTAND_BEING_USED;
116 
117 	return WALKTYPE_AUTOSTAND_BUT_NOT_FAR_ENOUGH;
118 }
119 
120 /**
121  * @brief Returns the number of the actor in the teamlist.
122  * @param[in] le The actor to search.
123  * @return The number of the actor in the teamlist. Or @c -1 if the given entity is not in the team list.
124  */
CL_ActorGetNumber(const le_t * le)125 int CL_ActorGetNumber (const le_t* le)
126 {
127 	int actorIdx;
128 
129 	assert(le);
130 
131 	for (actorIdx = 0; actorIdx < cl.numTeamList; actorIdx++) {
132 		if (cl.teamList[actorIdx] == le)
133 			return actorIdx;
134 	}
135 	return -1;
136 }
137 
138 /**
139  * @brief Returns the local entity information for a character in the team list
140  * @param[in] chr The character to search the local entity for.
141  * @return A pointer to a le_t struct.
142  */
CL_ActorGetFromCharacter(const character_t * chr)143 le_t* CL_ActorGetFromCharacter (const character_t* chr)
144 {
145 	for (int i = 0; i < cl.numTeamList; ++i) {
146 		if (cl.teamList[i] && cl.teamList[i]->ucn == chr->ucn)
147 			return cl.teamList[i];
148 	}
149 	return nullptr;
150 }
151 
152 /**
153  * @brief Returns the character information for an actor in the teamlist.
154  * @param[in] le The actor to search.
155  * @return A pointer to a character struct.
156  */
CL_ActorGetChr(const le_t * le)157 character_t* CL_ActorGetChr (const le_t* le)
158 {
159 	const linkedList_t* chrList = cl.chrList;
160 
161 	LIST_Foreach(chrList, character_t, chr) {
162 		if (chr->ucn == le->ucn)
163 			return chr;
164 	}
165 
166 	return nullptr;
167 }
168 
169 /**
170  * @param[in] shooter The local entity to get the reaction fire firedef from
171  * @return The current selected firedef for reaction fire or @c nullptr if there is none
172  */
CL_ActorGetReactionFireFireDef(const le_t * shooter)173 const fireDef_t* CL_ActorGetReactionFireFireDef (const le_t* shooter)
174 {
175 	const character_t* chr = CL_ActorGetChr(shooter);
176 	if (chr == nullptr)
177 		return nullptr;
178 
179 	const FiremodeSettings &fmSetting = chr->RFmode;
180 	const Item* weapon = shooter->getHandItem(fmSetting.getHand());
181 	if (weapon == nullptr)
182 		return nullptr;
183 
184 	const fireDef_t* fdArray = weapon->getFiredefs();
185 	if (fdArray == nullptr)
186 		return nullptr;
187 
188 	const int fmIdx = fmSetting.getFmIdx();
189 	if (fmIdx < 0 || fmIdx >= MAX_FIREDEFS_PER_WEAPON)
190 		return nullptr;
191 
192 	const fireDef_t* fd = &fdArray[fmIdx];
193 	return fd;
194 }
195 
196 /**
197  * @param[in] shooter The local entity to get the reaction fire definition for the range check for
198  * @param[in] target The target to calculate the distance to
199  * @return @c true if the given @c target is out of range for the @c shooter with the current selected fire mode
200  */
CL_ActorIsReactionFireOutOfRange(const le_t * shooter,const le_t * target)201 bool CL_ActorIsReactionFireOutOfRange (const le_t* shooter, const le_t* target)
202 {
203 	const float distance = VectorDist(shooter->origin, target->origin);
204 	const fireDef_t* fd = CL_ActorGetReactionFireFireDef(shooter);
205 	const bool outOfRange = fd->range < distance;
206 	return outOfRange;
207 }
208 
209 /**
210  * @brief Returns the amount of reserved TUs for a certain type.
211  * @param[in] le The actor to check.
212  * @param[in] type The type to check. Use RES_ALL_ACTIVE to get all reserved TUs that are not "active" (e.g. RF is skipped if disabled). RES_ALL returns ALL of them, no matter what. See reservation_types_t for a list of options.
213  * @return The reserved TUs for the given type.
214  * @return -1 on error.
215  */
CL_ActorReservedTUs(const le_t * le,const reservation_types_t type)216 int CL_ActorReservedTUs (const le_t* le, const reservation_types_t type)
217 {
218 	character_t* chr;
219 	int reservedReaction, reservedCrouch, reservedShot;
220 
221 	if (!le)
222 		return -1;
223 
224 	chr = CL_ActorGetChr(le);
225 	if (!chr) {
226 		Com_DPrintf(DEBUG_CLIENT, "CL_ActorReservedTUs: No character found for le.\n");
227 		return -1;
228 	}
229 
230 	reservedReaction = std::max(0, chr->reservedTus.reaction);
231 	reservedCrouch = std::max(0, chr->reservedTus.crouch);
232 	reservedShot = std::max(0, chr->reservedTus.shot);
233 
234 	switch (type) {
235 	case RES_ALL:
236 		/* A summary of ALL TUs that are reserved. */
237 		return reservedReaction + reservedCrouch + reservedShot;
238 	case RES_ALL_ACTIVE: {
239 		/* A summary of ALL TUs that are reserved depending on their "status". */
240 		/* Only use reaction-value if we have RF activated. */
241 		if ((le->state & STATE_REACTION))
242 			return reservedReaction + reservedShot + reservedCrouch;
243 		else
244 			return reservedShot + reservedCrouch;
245 	}
246 	case RES_REACTION:
247 		return reservedReaction;
248 	case RES_CROUCH:
249 		return reservedCrouch;
250 	case RES_SHOT:
251 		return reservedShot;
252 	default:
253 		Com_DPrintf(DEBUG_CLIENT, "CL_ActorReservedTUs: Bad type given: %i\n", type);
254 		return -1;
255 	}
256 }
257 
258 /**
259  * @brief Returns the amount of usable (overall-reserved) TUs for an actor.
260  * @param[in] le The actor to check.
261  * @return The remaining/usable TUs for this actor
262  * @return -1 on error (this includes bad [very large] numbers stored in the struct).
263  */
CL_ActorUsableTUs(const le_t * le)264 int CL_ActorUsableTUs (const le_t* le)
265 {
266 	if (!le)
267 		return -1;
268 
269 	return le->TU - CL_ActorReservedTUs(le, RES_ALL_ACTIVE);
270 }
271 
272 /**
273  * @brief Replace the reserved TUs for a certain type.
274  * @param[in] le The actor to change it for.
275  * @param[in] type The reservation type to be changed (i.e be replaced).
276  * @param[in] tus How many TUs to set.
277  */
CL_ActorReserveTUs(const le_t * le,const reservation_types_t type,const int tus)278 void CL_ActorReserveTUs (const le_t* le, const reservation_types_t type, const int tus)
279 {
280 	character_t* chr;
281 
282 	assert(type != RES_REACTION);
283 
284 	if (!le || tus < 0)
285 		return;
286 
287 	chr = CL_ActorGetChr(le);
288 	if (chr) {
289 		chrReservations_t res = chr->reservedTus;
290 
291 		if (type == RES_CROUCH)
292 			res.crouch = tus;
293 		else if (type == RES_SHOT)
294 			res.shot = tus;
295 
296 		MSG_Write_PA(PA_RESERVE_STATE, le->entnum, res.shot, res.crouch);
297 	}
298 }
299 
300 /**
301  * @brief Returns the actor injury modifier of the specified type.
302  * @param[in] le The actor.
303  * @param[in] type The injury modifier type.
304  * @return The injury modifier for this actor.
305  */
CL_ActorInjuryModifier(const le_t * le,const modifier_types_t type)306 float CL_ActorInjuryModifier (const le_t* le, const modifier_types_t type)
307 {
308 	float mod = 0;
309 
310 	if (le) {
311 		const character_t* chr = CL_ActorGetChr(le);
312 		int bodyPart;
313 		if (!chr)
314 			return 0;
315 		const BodyData* bodyTemplate = chr->teamDef->bodyTemplate;
316 		for (bodyPart = 0; bodyPart < bodyTemplate->numBodyParts(); ++bodyPart) {
317 			const int threshold = le->maxHP * bodyTemplate->woundThreshold(bodyPart);
318 			const int injury = (le->wounds.woundLevel[bodyPart] + le->wounds.treatmentLevel[bodyPart] * 0.5);
319 			if (injury > threshold)
320 				mod += 2 * bodyTemplate->penalty(bodyPart, type) * injury / le->maxHP;
321 		}
322 
323 		switch (type) {
324 		case MODIFIER_REACTION:
325 			mod += CL_ActorInjuryModifier(le, MODIFIER_SHOOTING);
326 			break;
327 		case MODIFIER_ACCURACY:
328 		case MODIFIER_SHOOTING:
329 			++mod;
330 			break;
331 		case MODIFIER_MOVEMENT:
332 			mod = ceil(mod);
333 			break;
334 		default:
335 			Com_Printf("CL_ActorInjuryPenalty: Unused modifier type %i\n", type);
336 			mod = 0;
337 			break;
338 		}
339 	}
340 	return mod;
341 }
342 
343 /**
344  * @brief Find the TUs needed for the given fireDef taking into account the actor wound penalties.
345  * @param[in] le The actor.
346  * @param[in] fd The fire definition.
347  * @param[in] reaction Whether this is a normal or reaction fire shot.
348  * @return The TUs needed for the fireDef for this actor.
349  */
CL_ActorTimeForFireDef(const le_t * le,const fireDef_t * fd,bool reaction)350 int CL_ActorTimeForFireDef (const le_t* le, const fireDef_t* fd, bool reaction)
351 {
352 	if (!fd)
353 		return -1;
354 
355 	return fd->time * CL_ActorInjuryModifier(le, reaction ? MODIFIER_REACTION : MODIFIER_SHOOTING);
356 }
357 
358 /*
359 ==============================================================
360 ACTOR SELECTION AND TEAM LIST
361 ==============================================================
362 */
363 
364 /**
365  * @brief Adds the actor to the team list.
366  * @sa CL_ActorAppear
367  * @sa CL_ActorRemoveFromTeamList
368  * @param le Pointer to local entity struct
369  */
CL_ActorAddToTeamList(le_t * le)370 void CL_ActorAddToTeamList (le_t* le)
371 {
372 	int actorIdx;
373 	const size_t size = lengthof(cl.teamList);
374 
375 	/* test team */
376 	if (!le || le->team != cls.team || le->pnum != cl.pnum || LE_IsDead(le))
377 		return;
378 
379 	/* check list for that actor */
380 	actorIdx = CL_ActorGetNumber(le);
381 
382 	/* add it */
383 	if (actorIdx == -1) {
384 		/* check list length */
385 		if (cl.numTeamList >= size) {
386 			Com_Printf("Too many actors on the teamlist!\n");
387 			return;
388 		}
389 		cl.teamList[cl.numTeamList] = le;
390 		UI_ExecuteConfunc("hudenable %i", cl.numTeamList);
391 		cl.numTeamList++;
392 		if (cl.numTeamList == 1)
393 			CL_ActorSelectList(0);
394 	} else {
395 		UI_ExecuteConfunc("hudenable %i", actorIdx);
396 	}
397 }
398 
CL_ActorCleanup(le_t * le)399 void CL_ActorCleanup (le_t* le)
400 {
401 	cls.i.destroyInventory(&le->inv);
402 }
403 
404 /**
405  * @brief Removes an actor (from your team) from the team list.
406  * @sa CL_ActorStateChange
407  * @sa CL_ActorAddToTeamList
408  * @param[in,out] le Pointer to local entity struct of the actor of your team
409  */
CL_ActorRemoveFromTeamList(le_t * le)410 void CL_ActorRemoveFromTeamList (le_t* le)
411 {
412 	int i;
413 
414 	if (!le)
415 		return;
416 
417 	for (i = 0; i < cl.numTeamList; i++) {
418 		if (cl.teamList[i] == le) {
419 			if (!LE_IsStunned(le)) {
420 				CL_ActorCleanup(le);
421 				/* remove from list */
422 				cl.teamList[i] = nullptr;
423 			} else {
424 				/** @todo why the heck is that needed? the inventory was already dropped to floor. */
425 				le->left = le->right = le->headgear = NONE;
426 				cls.i.destroyInventory(&le->inv);
427 			}
428 
429 			/* disable hud button */
430 			UI_ExecuteConfunc("huddisable %i", i);
431 
432 			break;
433 		}
434 	}
435 
436 	/* check selection */
437 	if (LE_IsSelected(le)) {
438 		for (i = 0; i < cl.numTeamList; i++) {
439 			le_t* tl = cl.teamList[i];
440 			if (tl && CL_ActorSelect(tl))
441 				break;
442 		}
443 
444 		if (i == cl.numTeamList)
445 			CL_ActorSelect(nullptr);
446 	}
447 }
448 
449 /**
450  * @brief Selects an actor.
451  * @param le Pointer to local entity struct. If this is @c nullptr the ui_inventory that is linked from the actors
452  * @sa CL_UGVCvars
453  * @sa CL_ActorCvars
454  */
CL_ActorSelect(le_t * le)455 bool CL_ActorSelect (le_t* le)
456 {
457 	int actorIdx;
458 	character_t* chr;
459 
460 	/* test team */
461 	if (!le) {
462 		if (selActor)
463 			selActor->flags &= ~LE_SELECTED;
464 		selActor = nullptr;
465 		ui_inventory = nullptr;
466 		return false;
467 	}
468 
469 	if (le->pnum != cl.pnum || LE_IsDead(le) || !le->inuse)
470 		return false;
471 
472 	if (LE_IsSelected(le)) {
473 		mousePosTargettingAlign = 0;
474 		return true;
475 	}
476 
477 	if (selActor)
478 		selActor->flags &= ~LE_SELECTED;
479 
480 	mousePosTargettingAlign = 0;
481 	selActor = le;
482 	selActor->flags |= LE_SELECTED;
483 	ui_inventory = &selActor->inv;
484 
485 	if (le->state & RF_IRGOGGLESSHOT)
486 		refdef.rendererFlags |= RDF_IRGOGGLES;
487 	else
488 		refdef.rendererFlags &= ~RDF_IRGOGGLES;
489 
490 	if (le->clientAction != nullptr)
491 		UI_ExecuteConfunc("enable_clientaction");
492 	else
493 		UI_ExecuteConfunc("disable_clientaction");
494 
495 	actorIdx = CL_ActorGetNumber(le);
496 	if (actorIdx == -1)
497 		return false;
498 
499 	/* console commands, update cvars */
500 	Cvar_ForceSet("cl_selected", va("%i", actorIdx));
501 
502 	chr = CL_ActorGetChr(le);
503 	if (!chr)
504 		Com_Error(ERR_DROP, "No character given for local entity!");
505 
506 	CL_UpdateCharacterValues(chr);
507 
508 	CL_ActorConditionalMoveCalc(le);
509 
510 	return true;
511 }
512 
513 /**
514  * @brief Selects an actor from a list.
515  *
516  * This function is used to select an actor from the lists that are
517  * used in equipment and team assemble screens
518  *
519  * @param num The index value from the list of actors
520  *
521  * @sa CL_ActorSelect
522  * @return true if selection was possible otherwise false
523  */
CL_ActorSelectList(int num)524 bool CL_ActorSelectList (int num)
525 {
526 	le_t* le;
527 
528 	/* check if actor exists */
529 	if (num >= cl.numTeamList || num < 0)
530 		return false;
531 
532 	/* select actor */
533 	le = cl.teamList[num];
534 	if (!le || !CL_ActorSelect(le))
535 		return false;
536 
537 	/* center view (if wanted) */
538 	LE_CenterView(le);
539 	Cvar_SetValue("cl_worldlevel", le->pos[2]);
540 
541 	return true;
542 }
543 
544 /**
545  * @brief selects the next actor
546  */
CL_ActorSelectNext(void)547 bool CL_ActorSelectNext (void)
548 {
549 	int selIndex = -1;
550 	const int num = cl.numTeamList;
551 	int i;
552 
553 	/* find index of currently selected actor */
554 	for (i = 0; i < num; i++) {
555 		const le_t* le = cl.teamList[i];
556 		if (le && le->inuse && LE_IsSelected(le) && !LE_IsDead(le)) {
557 			selIndex = i;
558 			break;
559 		}
560 	}
561 	if (selIndex < 0)
562 		return false;			/* no one selected? */
563 
564 	/* cycle round */
565 	i = selIndex;
566 	while (true) {
567 		i = (i + 1) % num;
568 		if (i == selIndex)
569 			break;
570 		if (CL_ActorSelectList(i))
571 			return true;
572 	}
573 	return false;
574 }
575 
576 /**
577  * @brief selects the previous actor
578  */
CL_ActorSelectPrev(void)579 bool CL_ActorSelectPrev (void)
580 {
581 	int selIndex = -1;
582 	const int num = cl.numTeamList;
583 	int i;
584 
585 	/* find index of currently selected actor */
586 	for (i = 0; i < num; i++) {
587 		const le_t* le = cl.teamList[i];
588 		if (le && le->inuse && LE_IsSelected(le) && !LE_IsDead(le)) {
589 			selIndex = i;
590 			break;
591 		}
592 	}
593 	if (selIndex < 0)
594 		return false;			/* no one selected? */
595 
596 	/* cycle round */
597 	i = selIndex;
598 	while (true) {
599 	/*	i = (i - 1) % num; */
600 		i--; if (i < 0) i = num - 1;
601 		if (i == selIndex)
602 			break;
603 		if (CL_ActorSelectList(i))
604 			return true;
605 	}
606 	return false;
607 }
608 
609 
610 /*
611 ==============================================================
612 ACTOR MOVEMENT AND SHOOTING
613 ==============================================================
614 */
615 
616 /**
617  * @brief A list of locations that cannot be moved to.
618  * @note Pointer to le->pos or edict->pos followed by le->fieldSize or edict->fieldSize
619  * @see CL_BuildForbiddenList
620  */
621 static pos_t* forbiddenList[MAX_FORBIDDENLIST];
622 /**
623  * @brief Current length of fb_list.
624  * @note all byte pointers in the fb_list list (pos + fieldSize)
625  * @see fb_list
626  */
627 static int forbiddenListLength;
628 
629 /**
630  * @brief Builds a list of locations that cannot be moved to (client side).
631  * @sa G_MoveCalc
632  * @sa G_BuildForbiddenList <- server side
633  * @sa Grid_CheckForbidden
634  * @note This is used for pathfinding.
635  * It is a list of where the selected unit can not move to because others are standing there already.
636  */
CL_BuildForbiddenList(void)637 static void CL_BuildForbiddenList (void)
638 {
639 	le_t* le = nullptr;
640 
641 	forbiddenListLength = 0;
642 
643 	while ((le = LE_GetNextInUse(le))) {
644 		if (LE_IsInvisible(le))
645 			continue;
646 		/* Dead ugv will stop walking, too. */
647 		if (le->type == ET_ACTOR2x2 || (!LE_IsStunned(le) && LE_IsLivingAndVisibleActor(le))) {
648 			forbiddenList[forbiddenListLength++] = le->pos;
649 			forbiddenList[forbiddenListLength++] = (byte*)&le->fieldSize;
650 		}
651 	}
652 
653 #ifdef PARANOID
654 	if (forbiddenListLength > MAX_FORBIDDENLIST)
655 		Com_Error(ERR_DROP, "CL_BuildForbiddenList: list too long!");
656 #endif
657 }
658 
659 #ifdef DEBUG
660 /**
661  * @brief Draws a marker for all blocked map-positions.
662  * @note currently uses basically the same code as CL_BuildForbiddenList
663  * @note usage in console: "debug_drawblocked"
664  * @todo currently the particles stay a _very_ long time ... so everybody has to stand still in order for the display to be correct.
665  * @sa CL_BuildForbiddenList
666  */
CL_DisplayBlockedPaths_f(void)667 static void CL_DisplayBlockedPaths_f (void)
668 {
669 	le_t* le = nullptr;
670 	int j;
671 	ptl_t* ptl;
672 	vec3_t s;
673 
674 	while ((le = LE_GetNextInUse(le))) {
675 		switch (le->type) {
676 		case ET_ACTOR:
677 		case ET_ACTOR2x2:
678 			/* draw blocking cursor at le->pos */
679 			if (!LE_IsDead(le))
680 				Grid_PosToVec(cl.mapData->routing, le->fieldSize, le->pos, s);
681 			break;
682 		case ET_DOOR:
683 		case ET_BREAKABLE:
684 		case ET_ROTATING:
685 			VectorCopy(le->origin, s);
686 			break;
687 		default:
688 			continue;
689 		}
690 
691 		ptl = CL_ParticleSpawn("blocked_field", 0, s, nullptr, nullptr);
692 		ptl->rounds = 2;
693 		ptl->roundsCnt = 2;
694 		ptl->life = 10000;
695 		ptl->t = 0;
696 		if (le->fieldSize == ACTOR_SIZE_2x2) {
697 			/* If this actor blocks 4 fields draw them as well. */
698 			for (j = 0; j < 3; j++) {
699 				ptl_t* ptl2 = CL_ParticleSpawn("blocked_field", 0, s, nullptr, nullptr);
700 				ptl2->rounds = ptl->rounds;
701 				ptl2->roundsCnt = ptl->roundsCnt;
702 				ptl2->life = ptl->life;
703 				ptl2->t = ptl->t;
704 			}
705 		}
706 	}
707 }
708 #endif
709 
710 /**
711  * @brief Recalculate forbidden list, available moves and actor's move length
712  * for the current selected actor.
713  * @note An attempt to do this with le->TU to save time ended up with the first actor not being able to move at gamestart.
714  * @todo seems like this function is called *before* the TUs are set
715  */
CL_ActorConditionalMoveCalc(le_t * le)716 void CL_ActorConditionalMoveCalc (le_t* le)
717 {
718 	CL_BuildForbiddenList();
719 	if (le && LE_IsSelected(le)) {
720 		Grid_CalcPathing(cl.mapData->routing, le->fieldSize, &cl.pathMap, le->pos, MAX_ROUTE_TUS, forbiddenList, forbiddenListLength);
721 		CL_ActorResetMoveLength(le);
722 	}
723 }
724 
725 /**
726  * @brief Returns the actor that is closest to the given origin
727  */
CL_ActorGetClosest(const vec3_t origin,int team)728 le_t* CL_ActorGetClosest (const vec3_t origin, int team)
729 {
730 	le_t* closest = nullptr;
731 	le_t* le = nullptr;
732 	while ((le = LE_GetNextInUse(le))) {
733 		if (le->team != team || !LE_IsLivingAndVisibleActor(le))
734 			continue;
735 
736 		if (closest == nullptr || VectorDist(le->origin, origin) < VectorDist(closest->origin, origin))
737 			closest = le;
738 	}
739 	return closest;
740 }
741 
742 /**
743  * @brief Checks that an action is valid.
744  * @param[in] le Pointer to actor for which we check an action.
745  * @return true if action is valid.
746  */
CL_ActorCheckAction(const le_t * le)747 int CL_ActorCheckAction (const le_t* le)
748 {
749 	if (!le)
750 		return false;
751 
752 	if (le->isMoving())
753 		return false;
754 
755 	if (!cls.isOurRound()) {
756 		HUD_DisplayMessage(_("It is not your turn!"));
757 		return false;
758 	}
759 
760 	return true;
761 }
762 
763 /**
764  * @brief Get the real move length (depends on crouch-state of the current actor).
765  * @note The part of the line that is not reachable in this turn (i.e. not enough
766  * @note TUs left) will be drawn differently.
767  * @param[in] to The position in the map to calculate the move-length for.
768  * @param[in] le Pointer to actor for which we calculate move lenght.
769  * @return The amount of TUs that are needed to walk to the given grid position
770  */
CL_ActorMoveLength(const le_t * le,const pos3_t to)771 static byte CL_ActorMoveLength (const le_t* le, const pos3_t to)
772 {
773 	const bool useAutostand = LE_IsCrouched(le) && cl_autostand->integer
774 				&& Grid_ShouldUseAutostand(&cl.pathMap, to);
775 	const int autostandTU = useAutostand ? 2 * TU_CROUCH : 0;
776 	byte crouchingState = LE_IsCrouched(le) && !useAutostand ? 1 : 0;
777 	const int length = Grid_MoveLength(&cl.pathMap, to, crouchingState, false);
778 	int dvec, numSteps = 0;
779 	pos3_t pos;
780 
781 	if (!length || length == ROUTING_NOT_REACHABLE)
782 		return length;
783 
784 	VectorCopy(to, pos);
785 	while ((dvec = Grid_MoveNext(&cl.pathMap, pos, crouchingState)) != ROUTING_UNREACHABLE) {
786 		++numSteps;
787 		PosSubDV(pos, crouchingState, dvec); /* We are going backwards to the origin. */
788 	}
789 
790 	return std::min(ROUTING_NOT_REACHABLE, length + static_cast<int>(numSteps
791 			* CL_ActorInjuryModifier(le, MODIFIER_MOVEMENT)) + autostandTU);
792 }
793 
794 /**
795  * @brief Recalculates the currently selected Actor's move length.
796  * @param[in,out] le Pointer to actor for which we reset move lenght.
797  */
CL_ActorResetMoveLength(le_t * le)798 void CL_ActorResetMoveLength (le_t* le)
799 {
800 	le->actorMoveLength = CL_ActorMoveLength(le, mousePos);
801 }
802 
803 /**
804  * @brief Draws the way to walk when confirm actions is activated.
805  * @param[in] to The location we draw the line to (starting with the location of selActor)
806  * @return true if everything went ok, otherwise false.
807  * @sa CL_MaximumMove (similar algo.)
808  * @sa CL_AddTargetingBox
809  */
CL_ActorTraceMove(const pos3_t to)810 static bool CL_ActorTraceMove (const pos3_t to)
811 {
812 	byte length;
813 	vec3_t vec, oldVec;
814 	pos3_t pos;
815 	int dvec;
816 	byte crouchingState;
817 
818 	if (!selActor)
819 		return false;
820 
821 	length = CL_ActorMoveLength(selActor, to);
822 	if (!length || length >= ROUTING_NOT_REACHABLE)
823 		return false;
824 
825 	crouchingState = LE_IsCrouched(selActor) ? 1 : 0;
826 
827 	Grid_PosToVec(cl.mapData->routing, selActor->fieldSize, to, oldVec);
828 	VectorCopy(to, pos);
829 
830 	while ((dvec = Grid_MoveNext(&cl.pathMap, pos, crouchingState)) != ROUTING_UNREACHABLE) {
831 		length = CL_ActorMoveLength(selActor, pos);
832 		PosSubDV(pos, crouchingState, dvec); /* We are going backwards to the origin. */
833 		Grid_PosToVec(cl.mapData->routing, selActor->fieldSize, pos, vec);
834 		if (length > CL_ActorUsableTUs(selActor))
835 			CL_ParticleSpawn("longRangeTracer", 0, vec, oldVec);
836 		else if (crouchingState)
837 			CL_ParticleSpawn("crawlTracer", 0, vec, oldVec);
838 		else
839 			CL_ParticleSpawn("moveTracer", 0, vec, oldVec);
840 		VectorCopy(vec, oldVec);
841 	}
842 	return true;
843 }
844 
845 /**
846  * @brief Return the last position we can walk to with a defined amount of TUs.
847  * @param[in] to The location we want to reach.
848  * @param[in] le Pointer to an actor for which we check maximum move.
849  * @param[in,out] pos The location we can reach with the given amount of TUs.
850  * @sa CL_TraceMove (similar algo.)
851  */
CL_ActorMaximumMove(const pos3_t to,const le_t * le,pos3_t pos)852 static void CL_ActorMaximumMove (const pos3_t to, const le_t* le, pos3_t pos)
853 {
854 	int dvec;
855 	byte crouchingState = le && LE_IsCrouched(le) ? 1 : 0;
856 	const int tus = CL_ActorUsableTUs(le);
857 	const byte length = CL_ActorMoveLength(le, to);
858 	if (!length || length >= ROUTING_NOT_REACHABLE)
859 		return;
860 
861 	VectorCopy(to, pos);
862 
863 	while ((dvec = Grid_MoveNext(&cl.pathMap, pos, crouchingState)) != ROUTING_UNREACHABLE) {
864 		const byte length2 = CL_ActorMoveLength(le, pos);
865 		if (length2 <= tus)
866 			return;
867 		PosSubDV(pos, crouchingState, dvec); /* We are going backwards to the origin. */
868 	}
869 }
870 
CL_ActorSetMode(le_t * actor,actorModes_t actorMode)871 void CL_ActorSetMode (le_t* actor, actorModes_t actorMode)
872 {
873 	actor->actorMode = actorMode;
874 }
875 
876 /**
877  * @brief Starts moving actor.
878  * @param[in] le
879  * @param[in] to
880  * @sa CL_ActorActionMouse
881  * @sa CL_ActorSelectMouse
882  */
CL_ActorStartMove(le_t * le,const pos3_t to)883 void CL_ActorStartMove (le_t* le, const pos3_t to)
884 {
885 	byte length;
886 	pos3_t toReal;
887 
888 	if (IN_GetMouseSpace() != MS_WORLD)
889 		return;
890 
891 	if (!CL_ActorCheckAction(le))
892 		return;
893 
894 	length = CL_ActorMoveLength(le, to);
895 
896 	if (!length || length >= ROUTING_NOT_REACHABLE) {
897 		/* move not valid, don't even care to send */
898 		return;
899 	}
900 
901 	/* Get the last position we can walk to with the usable TUs. */
902 	CL_ActorMaximumMove(to, le, toReal);
903 
904 	/* Get the cost of the new position just in case. */
905 	length = CL_ActorMoveLength(le, toReal);
906 
907 	if (CL_ActorUsableTUs(le) < length) {
908 		/* We do not have enough _usable_ TUs to move so don't even try to send. */
909 		/* This includes a check for reserved TUs (which isn't done on the server!) */
910 		return;
911 	}
912 
913 	/* change mode to move now */
914 	CL_ActorSetMode(le, M_MOVE);
915 
916 	/* move seems to be possible; send request to server */
917 	MSG_Write_PA(PA_MOVE, le->entnum, toReal);
918 }
919 
920 
921 /**
922  * @brief Shoot with actor.
923  * @param[in] le Who is shooting
924  * @param[in] at Position you are targeting to
925  */
CL_ActorShoot(const le_t * le,const pos3_t at)926 void CL_ActorShoot (const le_t* le, const pos3_t at)
927 {
928 	int type;
929 
930 	if (IN_GetMouseSpace() != MS_WORLD)
931 		return;
932 
933 	if (!CL_ActorCheckAction(le))
934 		return;
935 
936 	if (IS_MODE_FIRE_RIGHT(le->actorMode)) {
937 		type = ST_RIGHT;
938 	} else if (IS_MODE_FIRE_LEFT(le->actorMode)) {
939 		type = ST_LEFT;
940 	} else if (IS_MODE_FIRE_HEADGEAR(le->actorMode)) {
941 		type = ST_HEADGEAR;
942 	} else
943 		return;
944 
945 	MSG_Write_PA(PA_SHOOT, le->entnum, at, type, le->currentSelectedFiremode, mousePosTargettingAlign);
946 }
947 
948 /**
949  * @brief Searches the clip with the least TU usage to put it into the weapon
950  * @param invList The inventory list that can be used outside of this function for the found ammo
951  * @param inv The inventory to do the search in
952  * @param weapon The weapon to reload
953  * @return @c NONE if no container was found, the container id otherwise.
954  */
CL_ActorGetContainerForReload(Item ** invList,const Inventory * inv,const objDef_t * weapon)955 int CL_ActorGetContainerForReload (Item** invList, const Inventory* inv, const objDef_t* weapon)
956 {
957 	containerIndex_t container;
958 	int tu = 100;
959 	containerIndex_t bestContainer = NONE;
960 
961 	/* also search the linked ground floor tile (temp container) */
962 	for (container = 0; container < CID_MAX; ++container) {
963 		if (INVDEF(container)->out >= tu)
964 			continue;
965 		/* Once we've found at least one clip, there's no point
966 		 * searching other containers if it would take longer
967 		 * to retrieve the ammo from them than the one
968 		 * we've already found. */
969 		for (Item* ic = inv->getContainer2(container); ic; ic = ic->getNext()) {
970 			const objDef_t* od = ic->def();
971 			if (!od->isLoadableInWeapon(weapon) || !GAME_ItemIsUseable(od))
972 				continue;
973 			tu = INVDEF(container)->out;
974 			bestContainer = container;
975 			*invList = ic;
976 			break;
977 		}
978 	}
979 	return bestContainer;
980 }
981 
982 /**
983  * @brief Reload weapon with actor.
984  * @param[in,out] le The actor to reload the weapon for
985  * @param[in] containerID The container to reload
986  * @sa CL_ActorCheckAction
987  */
CL_ActorReload(le_t * le,containerIndex_t containerID)988 void CL_ActorReload (le_t* le, containerIndex_t containerID)
989 {
990 	Inventory* inv;
991 	Item* ic;
992 	const objDef_t* weapon;
993 	containerIndex_t bestContainer;
994 
995 	if (!CL_ActorCheckAction(le))
996 		return;
997 
998 	/* check weapon */
999 	inv = &le->inv;
1000 
1001 	if (inv->getContainer2(containerID)) {
1002 		weapon = inv->getContainer2(containerID)->def();
1003 	} else if (containerID == CID_LEFT && inv->getContainer2(CID_RIGHT)->isHeldTwoHanded()) {
1004 		/* Check for two-handed weapon */
1005 		containerID = CID_RIGHT;
1006 		weapon = inv->getContainer2(containerID)->def();
1007 	} else {
1008 		/* no weapon in the reloadable containers found */
1009 		return;
1010 	}
1011 
1012 	if (!weapon)
1013 		return;
1014 
1015 	/* return if the weapon is not reloadable */
1016 	if (!weapon->isReloadable())
1017 		return;
1018 
1019 	if (!GAME_ItemIsUseable(weapon)) {
1020 		HUD_DisplayMessage(_("You cannot reload this unknown item."));
1021 		return;
1022 	}
1023 
1024 	bestContainer = CL_ActorGetContainerForReload(&ic, inv, weapon);
1025 	/* send request */
1026 	if (bestContainer != NONE) {
1027 		int x, y;
1028 
1029 		ic->getFirstShapePosition(&x, &y);
1030 		x += ic->getX();
1031 		y += ic->getY();
1032 
1033 		CL_ActorInvMove(le, bestContainer, x, y, containerID, 0, 0);
1034 	}
1035 }
1036 
1037 /**
1038  * @brief Sends an inventory move event to the server
1039  * @param le The le that is doing the inventory move (an actor)
1040  * @param fromContainer The container to fetch the item from
1041  * @param fromX The x position in the container to get the item from
1042  * @param fromY The y position in the container to get the item from
1043  * @param toContainer The container to store the item in
1044  * @param toX The x position in the container to move the item to
1045  * @param toY The y position in the container to move the item to
1046  */
CL_ActorInvMove(const le_t * le,containerIndex_t fromContainer,int fromX,int fromY,containerIndex_t toContainer,int toX,int toY)1047 void CL_ActorInvMove (const le_t* le, containerIndex_t fromContainer, int fromX, int fromY, containerIndex_t toContainer, int toX, int toY)
1048 {
1049 	const invDef_t* fromPtr = INVDEF(fromContainer);
1050 
1051 	assert(CL_BattlescapeRunning());
1052 	assert(le);
1053 	assert(LE_IsActor(le));
1054 
1055 	const Item* item = le->inv.getItemAtPos(fromPtr, fromX, fromY);
1056 
1057 	if (item != nullptr) {
1058 		const character_t* chr = CL_ActorGetChr(le);
1059 		if (!le->inv.canHoldItemWeight(fromContainer, toContainer, *item, GAME_GetChrMaxLoad(chr))) {
1060 			UI_Popup(_("Warning"), _("This soldier can not carry anything else."));
1061 			return;
1062 		}
1063 		MSG_Write_PA(PA_INVMOVE, le->entnum, fromContainer, fromX, fromY, toContainer, toX, toY);
1064 	}
1065 }
1066 
1067 /**
1068  * @brief Uses the current selected entity in the battlescape. Can e.g. open the selected door.
1069  * @sa G_ClientUseEdict
1070  */
CL_ActorUse(const le_t * le)1071 static void CL_ActorUse (const le_t* le)
1072 {
1073 	if (!CL_ActorCheckAction(le))
1074 		return;
1075 
1076 	assert(le->clientAction);
1077 
1078 	MSG_Write_PA(PA_USE, le->entnum, le->clientAction->entnum);
1079 	Com_DPrintf(DEBUG_CLIENT, "CL_ActorUse: Use door number: %i (actor %i).\n", le->clientAction->entnum, le->entnum);
1080 }
1081 
1082 /**
1083  * @brief Hud callback to use the current selected entity
1084  */
CL_ActorUse_f(void)1085 static void CL_ActorUse_f (void)
1086 {
1087 	le_t* actor = selActor;
1088 
1089 	if (!CL_ActorCheckAction(actor))
1090 		return;
1091 
1092 	/* no client action */
1093 	if (actor->clientAction == nullptr) {
1094 		Com_DPrintf(DEBUG_CLIENT, "CL_ActorUse_f: No client_action set for actor with entnum %i.\n", actor->entnum);
1095 		return;
1096 	}
1097 
1098 	if (LE_IsDoor(actor->clientAction)) {
1099 		/* Check if we should even try to send this command (no TUs left or). */
1100 		if (CL_ActorUsableTUs(actor) >= TU_DOOR_ACTION)
1101 			CL_ActorUse(actor);
1102 	}
1103 }
1104 
1105 /**
1106  * @brief Checks whether we are in fire mode or node
1107  * @param mode The actor mode
1108  * @return @c true if we are in fire mode, @c false otherwise
1109  */
CL_ActorFireModeActivated(const actorModes_t mode)1110 bool CL_ActorFireModeActivated (const actorModes_t mode)
1111 {
1112 	return IS_MODE_FIRE_RIGHT(mode) || IS_MODE_FIRE_LEFT(mode) || IS_MODE_FIRE_HEADGEAR(mode);
1113 }
1114 
1115 /**
1116  * @brief Turns the actor around without moving
1117  */
CL_ActorTurnMouse(void)1118 void CL_ActorTurnMouse (void)
1119 {
1120 	vec3_t directionVector;
1121 	dvec_t dvec;
1122 
1123 	if (IN_GetMouseSpace() != MS_WORLD)
1124 		return;
1125 
1126 	if (!CL_ActorCheckAction(selActor))
1127 		return;
1128 
1129 	if (CL_ActorUsableTUs(selActor) < TU_TURN) {
1130 		/* Cannot turn because of not enough usable TUs. */
1131 		return;
1132 	}
1133 
1134 	/* check for fire-modes, and cancel them */
1135 	if (CL_ActorFireModeActivated(selActor->actorMode)) {
1136 		CL_ActorActionMouse();
1137 		return; /* and return without turning */
1138 	}
1139 
1140 	/* calculate dvec */
1141 	VectorSubtract(mousePos, selActor->pos, directionVector);
1142 	dvec = AngleToDV((int) (atan2(directionVector[1], directionVector[0]) * todeg));
1143 
1144 	/* send message to server */
1145 	MSG_Write_PA(PA_TURN, selActor->entnum, dvec);
1146 }
1147 
1148 /**
1149  * @brief Stands or crouches actor.
1150  */
CL_ActorStandCrouch_f(void)1151 static void CL_ActorStandCrouch_f (void)
1152 {
1153 	if (!CL_ActorCheckAction(selActor))
1154 		return;
1155 
1156 	/* In case of standing up also check the headroom */
1157 	if (LE_IsCrouched(selActor) && !RT_CanActorStandHere(cl.mapData->routing, selActor->fieldSize, selActor->pos))
1158 		return;
1159 
1160 	/* Check if we should even try to send this command (no TUs left or). */
1161 	if (CL_ActorUsableTUs(selActor) >= TU_CROUCH || CL_ActorReservedTUs(selActor, RES_CROUCH) >= TU_CROUCH) {
1162 		/* send a request to toggle crouch to the server */
1163 		MSG_Write_PA(PA_STATE, selActor->entnum, STATE_CROUCHED);
1164 	}
1165 }
1166 
1167 /**
1168  * @brief Toggles the headgear for the current selected player
1169  */
CL_ActorUseHeadgear_f(void)1170 static void CL_ActorUseHeadgear_f (void)
1171 {
1172 	const mouseSpace_t tmpMouseSpace = IN_GetMouseSpace();
1173 
1174 	/* this can be executed by a click on a hud button
1175 	 * but we need MS_WORLD mouse space to let the shooting
1176 	 * function work */
1177 	IN_SetMouseSpace(MS_WORLD);
1178 
1179 	if (!CL_ActorCheckAction(selActor))
1180 		return;
1181 
1182 	Item* headgear = selActor->inv.getHeadgear();
1183 	if (!headgear)
1184 		return;
1185 
1186 	CL_ActorSetMode(selActor, M_FIRE_HEADGEAR);
1187 	/** @todo make this a variable somewhere? */
1188 	selActor->currentSelectedFiremode = 0;
1189 	CL_ActorShoot(selActor, selActor->pos);
1190 	CL_ActorSetMode(selActor, M_MOVE);
1191 
1192 	/* restore old mouse space */
1193 	IN_SetMouseSpace(tmpMouseSpace);
1194 }
1195 
1196 /*
1197 ==============================================================
1198 MOUSE INPUT
1199 ==============================================================
1200 */
1201 
1202 /**
1203  * @brief handle select or action clicking in either move mode
1204  * @sa CL_ActorSelectMouse
1205  * @sa CL_ActorActionMouse
1206  */
CL_ActorMoveMouse(void)1207 static void CL_ActorMoveMouse (void)
1208 {
1209 	/* Don't display the cursor if it's above the currently selected level.
1210 	 * The 2nd part of the if is an attempt to display it anyway when we eg. climb a hill.
1211 	 * But there are too many situations inside buildings that match the criteria (eg. actorclip around chair).
1212 	 * So disabled for now.*/
1213 	if (mousePos[2] > cl_worldlevel->integer/* && !RT_AllCellsBelowAreFilled(cl.mapData->map, fieldSize, pos)*/)
1214 		return;
1215 
1216 	if (selActor->actorMode == M_PEND_MOVE) {
1217 		if (VectorCompare(mousePos, selActor->mousePendPos)) {
1218 			/* Pending move and clicked the same spot (i.e. 2 clicks on the same place) */
1219 			CL_ActorStartMove(selActor, mousePos);
1220 		} else {
1221 			/* Clicked different spot. */
1222 			VectorCopy(mousePos, selActor->mousePendPos);
1223 		}
1224 	} else {
1225 		/* either we want to confirm every move, or it's not our round and we prepare the
1226 		 * movement for the next round */
1227 		if (confirm_actions->integer || !cls.isOurRound()) {
1228 			/* Set our mode to pending move. */
1229 			VectorCopy(mousePos, selActor->mousePendPos);
1230 
1231 			CL_ActorSetMode(selActor, M_PEND_MOVE);
1232 		} else {
1233 			/* Just move there */
1234 			CL_ActorStartMove(selActor, mousePos);
1235 		}
1236 	}
1237 }
1238 
1239 /**
1240  * @brief Selects an actor using the mouse.
1241  * @sa CL_ActorStartMove
1242  */
CL_ActorSelectMouse(void)1243 void CL_ActorSelectMouse (void)
1244 {
1245 	if (IN_GetMouseSpace() != MS_WORLD || !selActor)
1246 		return;
1247 
1248 	switch (selActor->actorMode) {
1249 	case M_MOVE:
1250 	case M_PEND_MOVE:
1251 		/* Try and select another team member */
1252 		if (mouseActor && !LE_IsSelected(mouseActor) && CL_ActorSelect(mouseActor)) {
1253 			/* Succeeded so go back into move mode. */
1254 			CL_ActorSetMode(selActor, M_MOVE);
1255 		} else if (interactEntity) {
1256 			CL_ActorUse(selActor);
1257 		} else {
1258 			CL_ActorMoveMouse();
1259 		}
1260 		break;
1261 	case M_PEND_FIRE_R:
1262 	case M_PEND_FIRE_L:
1263 		if (VectorCompare(mousePos, selActor->mousePendPos)) {
1264 			/* Pending shot and clicked the same spot (i.e. 2 clicks on the same place) */
1265 			CL_ActorShoot(selActor, mousePos);
1266 
1267 			/* We switch back to aiming mode. */
1268 			if (selActor->actorMode == M_PEND_FIRE_R)
1269 				CL_ActorSetMode(selActor, M_FIRE_R);
1270 			else
1271 				CL_ActorSetMode(selActor, M_FIRE_L);
1272 		} else {
1273 			/* Clicked different spot. */
1274 			VectorCopy(mousePos, selActor->mousePendPos);
1275 		}
1276 		break;
1277 	case M_FIRE_R:
1278 		if (mouseActor && LE_IsSelected(mouseActor))
1279 			break;
1280 
1281 		/* We either switch to "pending" fire-mode or fire the gun. */
1282 		if (confirm_actions->integer == 1) {
1283 			CL_ActorSetMode(selActor, M_PEND_FIRE_R);
1284 			VectorCopy(mousePos, selActor->mousePendPos);
1285 		} else {
1286 			CL_ActorShoot(selActor, mousePos);
1287 		}
1288 		break;
1289 	case M_FIRE_L:
1290 		if (mouseActor && LE_IsSelected(mouseActor))
1291 			break;
1292 
1293 		/* We either switch to "pending" fire-mode or fire the gun. */
1294 		if (confirm_actions->integer == 1) {
1295 			CL_ActorSetMode(selActor, M_PEND_FIRE_L);
1296 			VectorCopy(mousePos, selActor->mousePendPos);
1297 		} else {
1298 			CL_ActorShoot(selActor, mousePos);
1299 		}
1300 		break;
1301 	default:
1302 		break;
1303 	}
1304 }
1305 
1306 
1307 /**
1308  * @brief initiates action with mouse.
1309  * @sa CL_ActionDown
1310  * @sa CL_ActorStartMove
1311  */
CL_ActorActionMouse(void)1312 void CL_ActorActionMouse (void)
1313 {
1314 	if (!selActor || IN_GetMouseSpace() != MS_WORLD)
1315 		return;
1316 
1317 	if (CL_ActorFireModeActivated(selActor->actorMode)) {
1318 		CL_ActorSetMode(selActor, M_MOVE);
1319 	}
1320 }
1321 
1322 /*
1323 ==============================================================
1324 MOUSE SCANNING
1325 ==============================================================
1326 */
1327 
1328 /**
1329  * @brief Get battlescape cell position under mouse cursor.
1330  * @note The returned position might be out of world boundaries, or under the ground etc.
1331  * @param[out] groundIntersection Point on the ground under the mouse cursor, in the world coordinates
1332  * @param[out] upperTracePoint Point in the sky under the mouse cursor, in the world coordinates
1333  * @param[out] lowerTracePoint Point below the ground under the mouse cursor, at the world boundary
1334  * @sa CL_ActorMouseTrace
1335  */
CL_GetWorldCoordsUnderMouse(vec3_t groundIntersection,vec3_t upperTracePoint,vec3_t lowerTracePoint)1336 void CL_GetWorldCoordsUnderMouse (vec3_t groundIntersection, vec3_t upperTracePoint, vec3_t lowerTracePoint)
1337 {
1338 	/* TODO: Move this to cl_battlescape.cpp? This functino is not directly related to actors. */
1339 	float cur[2], frustumSlope[2];
1340 	const float projectionDistance = 2048.0f;
1341 	float nDotP2minusP1;
1342 	vec3_t forward, right, up, stop;
1343 	vec3_t from, end;
1344 	vec3_t mapNormal, P3, P2minusP1;
1345 
1346 	/* get cursor position as a -1 to +1 range for projection */
1347 	cur[0] = (mousePosX * viddef.rx - viddef.viewWidth * 0.5 - viddef.x) / (viddef.viewWidth * 0.5);
1348 	cur[1] = (mousePosY * viddef.ry - viddef.viewHeight * 0.5 - viddef.y) / (viddef.viewHeight * 0.5);
1349 
1350 	/* get trace vectors */
1351 	VectorCopy(cl.cam.camorg, from);
1352 	VectorCopy(cl.cam.axis[0], forward);
1353 	VectorCopy(cl.cam.axis[1], right);
1354 	VectorCopy(cl.cam.axis[2], up);
1355 
1356 	if (cl_isometric->integer)
1357 		frustumSlope[0] = 10.0 * refdef.fieldOfViewX;
1358 	else
1359 		frustumSlope[0] = tan(refdef.fieldOfViewX * (M_PI / 360.0)) * projectionDistance;
1360 	frustumSlope[1] = frustumSlope[0] * ((float)viddef.viewHeight / (float)viddef.viewWidth);
1361 
1362 	/* transform cursor position into perspective space */
1363 	VectorMA(from, projectionDistance, forward, stop);
1364 	VectorMA(stop, cur[0] * frustumSlope[0], right, stop);
1365 	VectorMA(stop, cur[1] * -frustumSlope[1], up, stop);
1366 
1367 	/* in isometric mode the camera position has to be calculated from the cursor position so that the trace goes in the right direction */
1368 	if (cl_isometric->integer)
1369 		VectorMA(stop, -projectionDistance * 2, forward, from);
1370 
1371 	/* set stop point to the intersection of the trace line with the desired plane */
1372 	/* description of maths used:
1373 	 *   The equation for the plane can be written:
1374 	 *     mapNormal dot (end - P3) = 0
1375 	 *     where mapNormal is the vector normal to the plane,
1376 	 *         P3 is any point on the plane and
1377 	 *         end is the point where the line intersects the plane
1378 	 *   All points on the line can be calculated using:
1379 	 *     P1 + u*(P2 - P1)
1380 	 *     where P1 and P2 are points that define the line and
1381 	 *           u is some scalar
1382 	 *   The intersection of the line and plane occurs when:
1383 	 *     mapNormal dot (P1 + u*(P2 - P1)) == mapNormal dot P3
1384 	 *   The intersection therefore occurs when:
1385 	 *     u = (mapNormal dot (P3 - P1))/(mapNormal dot (P2 - P1))
1386 	 * Note: in the code below from & stop represent P1 and P2 respectively
1387 	 */
1388 	VectorSet(P3, 0., 0., cl_worldlevel->integer * UNIT_HEIGHT + CURSOR_OFFSET);
1389 	VectorSet(mapNormal, 0., 0., 1.);
1390 	VectorSubtract(stop, from, P2minusP1);
1391 	nDotP2minusP1 = DotProduct(mapNormal, P2minusP1);
1392 
1393 	/* calculate intersection directly if angle is not parallel to the map plane */
1394 	if (nDotP2minusP1 > 0.01 || nDotP2minusP1 < -0.01) {
1395 		float u;
1396 		vec3_t dir, P3minusP1;
1397 
1398 		VectorSubtract(P3, from, P3minusP1);
1399 		u = DotProduct(mapNormal, P3minusP1) / nDotP2minusP1;
1400 		VectorScale(P2minusP1, (vec_t)u, dir);
1401 		VectorAdd(from, dir, end);
1402 	} else { /* otherwise do a full trace */
1403 		CM_EntTestLineDM(cl.mapTiles, from, stop, end, TL_FLAG_ACTORCLIP, cl.leInlineModelList);
1404 	}
1405 
1406 	if (groundIntersection)
1407 		VectorCopy(end, groundIntersection);
1408 	if (upperTracePoint)
1409 		VectorCopy(from, upperTracePoint);
1410 	if (lowerTracePoint)
1411 		VectorCopy(stop, lowerTracePoint);
1412 }
1413 
1414 /**
1415  * @brief Battlescape cursor positioning.
1416  * @note Sets global var mouseActor to current selected le
1417  * @sa IN_Parse CL_GetWorldCoordsUnderMouse
1418  */
CL_ActorMouseTrace(void)1419 bool CL_ActorMouseTrace (void)
1420 {
1421 	vec3_t from, stop, end;
1422 	vec3_t pA, pB, pC;
1423 	pos3_t testPos;
1424 	le_t* interactLe;
1425 
1426 	CL_GetWorldCoordsUnderMouse(end, from, stop);
1427 	VecToPos(end, testPos);
1428 	/* hack to prevent cursor from getting stuck on the top of an invisible
1429 	 * playerclip surface (in most cases anyway) */
1430 	PosToVec(testPos, pA);
1431 	/* ensure that the cursor is in the world, if this is not done, the tracer box is
1432 	 * drawn in the void on the first level and the menu key bindings might get executed
1433 	 * this could result in different problems like the zooming issue (where you can't zoom
1434 	 * in again, because in_zoomout->state is not reseted). */
1435 	if (CL_OutsideMap(pA, MAP_SIZE_OFFSET))
1436 		return false;
1437 
1438 	VectorCopy(pA, pB);
1439 	pA[2] += UNIT_HEIGHT;
1440 	pB[2] -= UNIT_HEIGHT;
1441 	/** @todo Shouldn't we check the return value of CM_TestLineDM here - maybe
1442 	 * we don't have to do the second Grid_Fall call at all and can save a lot
1443 	 * of traces */
1444 	pos_t restingLevel = Grid_Fall(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), testPos);
1445 	CM_EntTestLineDM(cl.mapTiles, pA, pB, pC, TL_FLAG_ACTORCLIP, cl.leInlineModelList);
1446 	VecToPos(pC, testPos);
1447 	/* VecToPos strictly rounds the values down, while routing will round floors up to the next QUANT.
1448 	 * This makes a huge diffence when calculating the z-level:
1449 	 * without compensation, z of 61-63 will belong to the level below. */
1450 	testPos[2] = ModelFloorToQuant(pC[2] / CELL_HEIGHT);
1451 
1452 	restingLevel = std::min(restingLevel, Grid_Fall(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), testPos));
1453 
1454 	/* if grid below intersection level, start a trace from the intersection */
1455 	if (restingLevel < cl_worldlevel->integer) {
1456 		VectorCopy(end, from);
1457 		from[2] -= CURSOR_OFFSET;
1458 		CM_EntTestLineDM(cl.mapTiles, from, stop, end, TL_FLAG_ACTORCLIP, cl.leInlineModelList);
1459 		VecToPos(end, testPos);
1460 		restingLevel = Grid_Fall(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), testPos);
1461 	}
1462 
1463 	/* test if the selected grid is out of the world */
1464 	if (restingLevel >= PATHFINDING_HEIGHT)
1465 		return false;
1466 
1467 	/* Set truePos- test pos is under the cursor. */
1468 	VectorCopy(testPos, truePos);
1469 	truePos[2] = cl_worldlevel->integer;
1470 
1471 	/* Set mousePos to the position that the actor will move to. */
1472 	testPos[2] = restingLevel;
1473 	VectorCopy(testPos, mousePos);
1474 
1475 	interactLe = CL_BattlescapeSearchAtGridPos(mousePos, false, selActor);
1476 	if (interactLe != nullptr && LE_IsActor(interactLe)) {
1477 		mouseActor = interactLe;
1478 		interactEntity = nullptr;
1479 	} else if (selActor != nullptr && selActor->clientAction == interactLe) {
1480 		interactEntity = interactLe;
1481 		mouseActor = nullptr;
1482 	} else {
1483 		interactEntity = nullptr;
1484 		mouseActor = nullptr;
1485 	}
1486 
1487 	if (interactEntity != nullptr) {
1488 		SCR_ChangeCursor(2);
1489 	} else {
1490 		SCR_ChangeCursor(1);
1491 	}
1492 
1493 	/* calculate move length */
1494 	if (selActor && !VectorCompare(mousePos, mouseLastPos)) {
1495 		VectorCopy(mousePos, mouseLastPos);
1496 		CL_ActorResetMoveLength(selActor);
1497 	}
1498 
1499 	return true;
1500 }
1501 
1502 /**
1503  * @brief Scroll battlescape touchscreen-style, by clicking and dragging away
1504  */
CL_InitBattlescapeMouseDragging(void)1505 void CL_InitBattlescapeMouseDragging (void)
1506 {
1507 	CL_GetWorldCoordsUnderMouse(mouseDraggingPos, nullptr, nullptr);
1508 }
1509 
1510 /**
1511  * @brief Scroll battlescape touchscreen-style, by clicking and dragging away
1512  */
CL_BattlescapeMouseDragging(void)1513 void CL_BattlescapeMouseDragging (void)
1514 {
1515 	/* TODO: the movement is snapping to the cell center, and is clunky - make it smooth */
1516 	/* Difference between last and currently selected cell, we'll move camera by that difference */
1517 	vec3_t currentMousePos, mousePosDiff;
1518 
1519 	CL_GetWorldCoordsUnderMouse(currentMousePos, nullptr, nullptr);
1520 	if (fabs(currentMousePos[0] - mouseDraggingPos[0]) + fabs(currentMousePos[1] - mouseDraggingPos[1]) < 0.5f)
1521 		return;
1522 	VectorSubtract(mouseDraggingPos, currentMousePos, mousePosDiff);
1523 	VectorMA(cl.cam.origin, 0.2f, mousePosDiff, cl.cam.origin); /* Move camera slowly to the dest point, to prevent shaking */
1524 	Cvar_SetValue("cl_worldlevel", truePos[2]); /* Do not change world level */
1525 }
1526 
1527 /*
1528 ==============================================================
1529 ACTOR GRAPHICS
1530 ==============================================================
1531 */
1532 
1533 /**
1534  * @brief Checks whether a weapon should be added to the entity's hand
1535  * @param[in] objID The item id that the actor is holding in his hand (@c le->left or @c le->right)
1536  * @return true if the weapon is a valid item and false if it's a dummy item or the actor has nothing
1537  * in the given hand
1538  */
CL_AddActorWeapon(int objID)1539 static inline bool CL_AddActorWeapon (int objID)
1540 {
1541 	if (objID != NONE) {
1542 		const objDef_t* od = INVSH_GetItemByIDX(objID);
1543 		if (od->isVirtual)
1544 			return false;
1545 		return true;
1546 	}
1547 	return false;
1548 }
1549 
1550 /**
1551  * @brief Adds an actor to the render entities with all it's models and items.
1552  * @param[in] le The local entity to get the values from
1553  * @param[in] ent The body entity used in the renderer
1554  * @sa CL_AddUGV
1555  * @sa LE_AddToScene
1556  * @sa CL_ActorAppear
1557  * @note Called via addfunc for each local entity in every frame
1558  */
CL_AddActor(le_t * le,entity_t * ent)1559 bool CL_AddActor (le_t* le, entity_t* ent)
1560 {
1561 	entity_t add(RF_NONE);
1562 
1563 	if (!cl_showactors->integer)
1564 		return false;
1565 
1566 	const bool hasTagHead = R_GetTagIndexByName(le->model1, "tag_head") != -1;
1567 	const int delta = hasTagHead ? 2 : 1;
1568 
1569 	if (LE_IsStunned(le)) {
1570 		if (!le->ptl)
1571 			le->ptl = CL_ParticleSpawn("stunnedactor", 0, le->origin);
1572 	} else if (!LE_IsDead(le)) {
1573 		/* add the weapons to the actor's hands */
1574 		const bool addLeftHandWeapon = CL_AddActorWeapon(le->left);
1575 		const bool addRightHandWeapon = CL_AddActorWeapon(le->right);
1576 		/* add left hand weapon */
1577 		if (addLeftHandWeapon) {
1578 			add.init();
1579 
1580 			add.model = cls.modelPool[le->left];
1581 			if (!add.model)
1582 				Com_Error(ERR_DROP, "Actor model for left hand weapon wasn't found!");
1583 
1584 			/* point to the body ent which will be added last */
1585 			add.tagent = R_GetFreeEntity() + delta + addRightHandWeapon;
1586 			add.tagname = "tag_lweapon";
1587 
1588 			R_AddEntity(&add);
1589 		}
1590 
1591 		/* add right hand weapon */
1592 		if (addRightHandWeapon) {
1593 			add.init();
1594 
1595 			add.alpha = le->alpha;
1596 			add.model = cls.modelPool[le->right];
1597 			if (!add.model)
1598 				Com_Error(ERR_DROP, "Actor model for right hand weapon wasn't found!");
1599 
1600 			/* point to the body ent which will be added last */
1601 			add.tagent = R_GetFreeEntity() + delta;
1602 			add.tagname = "tag_rweapon";
1603 
1604 			R_AddEntity(&add);
1605 		}
1606 	}
1607 
1608 	if (hasTagHead) {
1609 		/* add head */
1610 		add.init();
1611 
1612 		add.alpha = le->alpha;
1613 		add.model = le->model2;
1614 		if (!add.model)
1615 			Com_Error(ERR_DROP, "Actor model wasn't found!");
1616 		add.skinnum = le->headSkin;
1617 
1618 		/* point to the body ent which will be added last */
1619 		add.tagent = R_GetFreeEntity() + 1;
1620 		add.tagname = "tag_head";
1621 
1622 		if (le->team != cls.team)
1623 			add.flags |= RF_IRGOGGLES;
1624 
1625 		R_AddEntity(&add);
1626 	}
1627 
1628 	/** Add actor special effects.
1629 	 * Only draw blood if the actor is dead or (if stunned) was damaged more than half its maximum HPs. */
1630 	/** @todo Better value for this?	*/
1631 	if (LE_IsStunned(le) && le->HP <= le->maxHP / 2)
1632 		ent->flags |= RF_BLOOD;
1633 	else if (LE_IsDead(le))
1634 		ent->flags |= RF_BLOOD;
1635 	else
1636 		ent->flags |= RF_SHADOW;
1637 
1638 	ent->flags |= RF_ACTOR;
1639 	/* actors are highlighted if some other actor uses ir goggles */
1640 	if (le->team != cls.team)
1641 		ent->flags |= RF_IRGOGGLES;
1642 
1643 	if (!LE_IsDead(le) && !LE_IsStunned(le)) {
1644 		if (LE_IsSelected(le))
1645 			ent->flags |= RF_SELECTED;
1646 		if (le->team == cls.team) {
1647 			if (le->pnum == cl.pnum)
1648 				ent->flags |= RF_MEMBER;
1649 			if (le->pnum != cl.pnum)
1650 				ent->flags |= RF_ALLIED;
1651 		} else {
1652 			ent->flags |= RF_OPPONENT;
1653 		}
1654 		if (le->team == TEAM_CIVILIAN)
1655 			ent->flags |= RF_NEUTRAL;
1656 	}
1657 
1658 	if (ent->flags & RF_BLOOD) {
1659 		const char* deathTextureName;
1660 		assert(le->teamDef != nullptr);
1661 		deathTextureName = le->teamDef->deathTextureName;
1662 		ent->texture = R_FindImage(deathTextureName, it_effect);
1663 	}
1664 
1665 	return true;
1666 }
1667 
1668 /*
1669 ==============================================================
1670 TARGETING GRAPHICS
1671 ==============================================================
1672 */
1673 
1674 /**
1675  * @brief Show weapon radius
1676  * @param[in] center The center of the circle
1677  * @param[in] radius The radius of the damage circle
1678  */
CL_TargetingRadius(const vec3_t center,const float radius)1679 static void CL_TargetingRadius (const vec3_t center, const float radius)
1680 {
1681 	ptl_t* particle = CL_ParticleSpawn("circle", 0, center);
1682 	if (particle != nullptr)
1683 		particle->size[0] = radius;
1684 }
1685 
1686 /**
1687  * @brief Draws line to target.
1688  * @param[in] fromPos The (grid-) position of the aiming actor.
1689  * @param[in] fromActorSize The size of the aiming actor (1 for 1x1 or 2 for 2x2).
1690  * @param[in] toPos The (grid-) position of the target.
1691  * @sa CL_TargetingGrenade
1692  * @sa CL_AddTargeting
1693  * @sa CL_Trace
1694  * @sa G_ShootSingle
1695  */
CL_TargetingStraight(const pos3_t fromPos,actorSizeEnum_t fromActorSize,const pos3_t toPos)1696 static void CL_TargetingStraight (const pos3_t fromPos, actorSizeEnum_t fromActorSize, const pos3_t toPos)
1697 {
1698 	vec3_t start, end;
1699 	vec3_t dir, mid, temp;
1700 	bool crossNo;
1701 	le_t* target = nullptr;
1702 	actorSizeEnum_t toActorSize;
1703 
1704 	if (!selActor || !selActor->fd)
1705 		return;
1706 
1707 	/* search for an actor at target */
1708 	target = CL_BattlescapeSearchAtGridPos(toPos, true, nullptr);
1709 
1710 	/* Determine the target's size. */
1711 	toActorSize = target
1712 		? target->fieldSize
1713 		: ACTOR_SIZE_NORMAL;
1714 
1715 	Grid_PosToVec(cl.mapData->routing, fromActorSize, fromPos, start);
1716 	Grid_PosToVec(cl.mapData->routing, toActorSize, toPos, end);
1717 	if (mousePosTargettingAlign)
1718 		end[2] -= mousePosTargettingAlign;
1719 
1720 	/* calculate direction */
1721 	VectorSubtract(end, start, dir);
1722 	VectorNormalize(dir);
1723 
1724 	/* calculate 'out of range point' if there is one */
1725 	if (VectorDistSqr(start, end) > selActor->fd->range * selActor->fd->range) {
1726 		VectorMA(start, selActor->fd->range, dir, mid);
1727 		crossNo = true;
1728 	} else {
1729 		VectorCopy(end, mid);
1730 		crossNo = false;
1731 	}
1732 
1733 	VectorMA(start, UNIT_SIZE * 1.4, dir, temp);
1734 	/* switch up to top level, this is needed to make sure our trace doesn't go through ceilings ... */
1735 	/** @todo is this really needed for straight targetting? - for grenades, yes, but not for straight no?
1736 	 * cl_worldlevel->integer should be enough here */
1737 	trace_t tr = CL_Trace(start, temp, AABB(), selActor, nullptr, MASK_SHOT, cl.mapMaxLevel - 1);
1738 	if (tr.le && (tr.le->team == cls.team || LE_IsCivilian(tr.le)) && LE_IsCrouched(tr.le))
1739 		VectorMA(start, UNIT_SIZE * 1.4, dir, temp);
1740 	else
1741 		VectorCopy(start, temp);
1742 
1743 	/** @todo is this really needed for straight targetting? - for grenades, yes, but not for straight no?
1744 	 * cl_worldlevel->integer should be enough here */
1745 	tr = CL_Trace(temp, mid, AABB(), selActor, target, MASK_SHOT, cl.mapMaxLevel - 1);
1746 
1747 	if (tr.fraction < 1.0 && (!tr.le || (!LE_IsInvisible(tr.le) && !VectorCompare(tr.le->pos, toPos)))) {
1748 		const float d = VectorDist(temp, mid);
1749 		VectorMA(start, tr.fraction * d, dir, mid);
1750 		crossNo = true;
1751 	}
1752 
1753 	/* spawn particles */
1754 	CL_ParticleSpawn("inRangeTracer", 0, start, mid);
1755 	if (crossNo) {
1756 		CL_ParticleSpawn("longRangeTracer", 0, mid, end);
1757 		CL_ParticleSpawn("cross_no", 0, end);
1758 	} else {
1759 		CL_ParticleSpawn("cross", 0, end);
1760 	}
1761 
1762 	if (selActor->fd->splrad > 0.0) {
1763 		Grid_PosToVec(cl.mapData->routing, toActorSize, toPos, end);
1764 		CL_TargetingRadius(end, selActor->fd->splrad);
1765 	}
1766 }
1767 
1768 #define GRENADE_PARTITIONS	20
1769 
1770 /**
1771  * @brief Shows targeting for a grenade.
1772  * @param[in] fromPos The (grid-) position of the aiming actor.
1773  * @param[in] fromActorSize The size of the aiming actor (1 for 1x1 or 2 for 2x2).
1774  * @param[in] toPos The (grid-) position of the target (mousePos or mousePendPos).
1775  * @sa CL_TargetingStraight
1776  */
CL_TargetingGrenade(const pos3_t fromPos,actorSizeEnum_t fromActorSize,const pos3_t toPos)1777 static void CL_TargetingGrenade (const pos3_t fromPos, actorSizeEnum_t fromActorSize, const pos3_t toPos)
1778 {
1779 	vec3_t from, at, cross;
1780 	float vz, dt;
1781 	vec3_t v0, ds, next;
1782 	bool obstructed = false;
1783 	int i;
1784 	le_t* target = nullptr;
1785 	actorSizeEnum_t toActorSize;
1786 
1787 	if (!selActor || !selActor->fd || Vector2Compare(fromPos, toPos))
1788 		return;
1789 
1790 	/* search for an actor at target */
1791 	target = CL_BattlescapeSearchAtGridPos(toPos, true, nullptr);
1792 
1793 	/* Determine the target's size. */
1794 	toActorSize = target
1795 		? target->fieldSize
1796 		: ACTOR_SIZE_NORMAL;
1797 
1798 	/* get vectors, paint cross */
1799 	Grid_PosToVec(cl.mapData->routing, fromActorSize, fromPos, from);
1800 	Grid_PosToVec(cl.mapData->routing, toActorSize, toPos, at);
1801 	from[2] += selActor->fd->shotOrg[1];
1802 
1803 	/* prefer to aim grenades at the ground */
1804 	at[2] -= GROUND_DELTA;
1805 	if (mousePosTargettingAlign)
1806 		at[2] -= mousePosTargettingAlign;
1807 	VectorCopy(at, cross);
1808 
1809 	/* calculate parabola */
1810 	dt = Com_GrenadeTarget(from, at, selActor->fd->range, selActor->fd->launched, selActor->fd->rolled, v0);
1811 	if (!dt) {
1812 		CL_ParticleSpawn("cross_no", 0, cross);
1813 		return;
1814 	}
1815 
1816 	dt /= GRENADE_PARTITIONS;
1817 	VectorSubtract(at, from, ds);
1818 	VectorScale(ds, 1.0 / GRENADE_PARTITIONS, ds);
1819 	ds[2] = 0;
1820 
1821 	/* paint */
1822 	vz = v0[2];
1823 
1824 	for (i = 0; i < GRENADE_PARTITIONS; i++) {
1825 		VectorAdd(from, ds, next);
1826 		next[2] += dt * (vz - 0.5 * GRAVITY * dt);
1827 		vz -= GRAVITY * dt;
1828 		VectorScale(ds, i + 1.0, at);
1829 
1830 		/* trace for obstacles. Switch up to top level, to make sure our trace
1831 		 * doesn't go through ceilings ... */
1832 		const trace_t tr = CL_Trace(from, next, AABB(), selActor, target, MASK_SHOT, cl.mapMaxLevel - 1);
1833 
1834 		/* something was hit */
1835 		if (tr.fraction < 1.0 && (!tr.le || (!LE_IsInvisible(tr.le) && !VectorCompare(tr.le->pos, toPos)))) {
1836 			obstructed = true;
1837 		}
1838 
1839 		/* draw particles */
1840 		/** @todo character strength should be used here, too
1841 		 * the stronger the character, the further the throw */
1842 		if (obstructed || VectorLength(at) > selActor->fd->range)
1843 			CL_ParticleSpawn("longRangeTracer", 0, from, next);
1844 		else
1845 			CL_ParticleSpawn("inRangeTracer", 0, from, next);
1846 		VectorCopy(next, from);
1847 	}
1848 	/* draw targeting cross */
1849 	if (obstructed || VectorLength(at) > selActor->fd->range)
1850 		CL_ParticleSpawn("cross_no", 0, cross);
1851 	else
1852 		CL_ParticleSpawn("cross", 0, cross);
1853 
1854 	if (selActor->fd->splrad > 0.0) {
1855 		Grid_PosToVec(cl.mapData->routing, toActorSize, toPos, at);
1856 		CL_TargetingRadius(at, selActor->fd->splrad);
1857 	}
1858 }
1859 
1860 /**
1861  * @brief field marker box
1862  * @sa ModelOffset
1863  */
1864 static const vec3_t halfBoxSize = { BOX_DELTA_WIDTH, BOX_DELTA_LENGTH, BOX_DELTA_HEIGHT };
1865 #define BoxOffset(aSize, target) (target[0]=(aSize-1)*(UNIT_SIZE+BOX_DELTA_WIDTH), target[1]=(aSize-1)*(UNIT_SIZE+BOX_DELTA_LENGTH), target[2]=0)
1866 
1867 /**
1868  * @brief create a targeting box at the given position
1869  * @sa CL_ParseClientinfo
1870  * @sa CL_TraceMove
1871  */
CL_AddTargetingBox(pos3_t pos,bool pendBox)1872 static void CL_AddTargetingBox (pos3_t pos, bool pendBox)
1873 {
1874 	if (!cl_showactors->integer)
1875 		return;
1876 
1877 	entity_t cursor(RF_BOX);
1878 
1879 	/* Paint the green box if move is possible ...
1880 	 * OR paint a dark blue one if move is impossible or the
1881 	 * soldier does not have enough TimeUnits left. */
1882 	if (selActor && selActor->actorMoveLength < ROUTING_NOT_REACHABLE
1883 	 && selActor->actorMoveLength <= CL_ActorUsableTUs(selActor))
1884 		VectorSet(cursor.color, 0, 1, 0); /* Green */
1885 	else
1886 		VectorSet(cursor.color, 0.6, 0.68, 1); /* Light Blue */
1887 
1888 	/* color */
1889 	/* if the mouse is over an actor, but not the selected one */
1890 	actorSizeEnum_t actorSize = ACTOR_SIZE_NORMAL;
1891 	if (mouseActor && !LE_IsSelected(mouseActor)) {
1892 		actorSize = mouseActor->fieldSize;
1893 		cursor.alpha = 0.6 + 0.2 * sin((float) cl.time / 80);
1894 		/* Paint the box red if the soldiers under the cursor is
1895 		 * not in our team and no civilian either. */
1896 		if (mouseActor->team != cls.team) {
1897 			switch (mouseActor->team) {
1898 			case TEAM_CIVILIAN:
1899 				/* Civilians are yellow */
1900 				VectorSet(cursor.color, 1, 1, 0); /* Yellow */
1901 				break;
1902 			default:
1903 				if (LE_IsAlien(mouseActor)) {
1904 					if (GAME_TeamIsKnown(mouseActor->teamDef))
1905 						UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, _(mouseActor->teamDef->name));
1906 					else
1907 						UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, _("Unknown alien race"));
1908 				} else {
1909 					/* multiplayer names */
1910 					/* see CL_ParseClientinfo */
1911 					UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, CL_PlayerGetName(mouseActor->pnum));
1912 				}
1913 				/* Aliens (and players not in our team [multiplayer]) are red */
1914 				VectorSet(cursor.color, 1, 0, 0); /* Red */
1915 				break;
1916 			}
1917 		} else {
1918 			/* coop multiplayer games */
1919 			if (mouseActor->pnum != cl.pnum) {
1920 				UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, CL_PlayerGetName(mouseActor->pnum));
1921 			} else {
1922 				/* we know the names of our own actors */
1923 				character_t* chr = CL_ActorGetChr(mouseActor);
1924 				assert(chr);
1925 				UI_RegisterText(TEXT_MOUSECURSOR_PLAYERNAMES, chr->name);
1926 			}
1927 			/* Paint a light blue box if on our team */
1928 			VectorSet(cursor.color, 0.2, 0.3, 1); /* Light Blue */
1929 		}
1930 	} else {
1931 		/* either no actor under the cursor or the selected one */
1932 		cursor.alpha = 0.3;
1933 		if (selActor) { /* there should always be an actor selected, but who knows */
1934 			actorSize = selActor->fieldSize;
1935 		}
1936 	}
1937 
1938 	/* Now calculate the size of the cursor box, depending on the actor. */
1939 	/* For some strange reason we use origin and oldorigin instead of the ent's min/max, respectively */
1940 	Grid_PosToVec(cl.mapData->routing, ACTOR_SIZE_NORMAL, pos, cursor.origin);	/* center of the (lower left) cell */
1941 	VectorAdd(cursor.origin, halfBoxSize, cursor.oldorigin);
1942 	VectorSubtract(cursor.origin, halfBoxSize, cursor.origin);
1943 	if (actorSize > ACTOR_SIZE_NORMAL) {
1944 		vec_t inc = UNIT_SIZE * (actorSize - 1);
1945 		vec3_t increase = { inc, inc, 0};
1946 		VectorAdd(cursor.oldorigin, increase, cursor.oldorigin);
1947 	}
1948 
1949 	/* if pendBox is true then ignore all the previous color considerations and use cyan */
1950 	if (pendBox) {
1951 		VectorSet(cursor.color, 0, 1, 1); /* Cyan */
1952 		cursor.alpha = 0.15;
1953 	}
1954 
1955 	/* add it */
1956 	R_AddEntity(&cursor);
1957 }
1958 
1959 /**
1960  * @brief Targets to the ground when holding the assigned button
1961  * @sa mousePosTargettingAlign
1962  */
CL_ActorTargetAlign_f(void)1963 void CL_ActorTargetAlign_f (void)
1964 {
1965 	int align = GROUND_DELTA;
1966 
1967 	/* no firedef selected */
1968 	if (!selActor || !selActor->fd)
1969 		return;
1970 	if (!CL_ActorFireModeActivated(selActor->actorMode))
1971 		return;
1972 
1973 	/* user defined height align */
1974 	if (Cmd_Argc() == 2) {
1975 		align = atoi(Cmd_Argv(1));
1976 	} else {
1977 		static int currentPos = 0;
1978 		switch (currentPos) {
1979 		case 0:
1980 			if (selActor->fd->gravity)
1981 				align = -align;
1982 			currentPos = 1; /* next level */
1983 			break;
1984 		case 1:
1985 			/* only allow to align to lower z level if the actor is
1986 			 * standing at a higher z-level */
1987 			if (selActor->fd->gravity)
1988 				align = -(2 * align);
1989 			else
1990 				align = -align;
1991 			currentPos = 2;
1992 			break;
1993 		case 2:
1994 			/* the static var is not reseted on weaponswitch or actorswitch */
1995 			if (selActor->fd->gravity) {
1996 				align = 0;
1997 				currentPos = 0; /* next level */
1998 			} else {
1999 				align = -(2 * align);
2000 				currentPos = 3; /* next level */
2001 			}
2002 			break;
2003 		case 3:
2004 			align = 0;
2005 			currentPos = 0; /* back to start */
2006 			break;
2007 		}
2008 	}
2009 	mousePosTargettingAlign = align;
2010 }
2011 
2012 /**
2013  * @brief Adds a target cursor when we render the world.
2014  * @sa CL_TargetingStraight
2015  * @sa CL_TargetingGrenade
2016  * @sa CL_AddTargetingBox
2017  * @sa CL_TraceMove
2018  * @sa CL_ViewRender
2019  * Draws the tracer (red, yellow, green box) on the grid
2020  */
CL_AddTargeting(void)2021 void CL_AddTargeting (void)
2022 {
2023 	if (IN_GetMouseSpace() != MS_WORLD || !selActor)
2024 		return;
2025 
2026 	switch (selActor->actorMode) {
2027 	case M_MOVE:
2028 	case M_PEND_MOVE:
2029 		/* Don't display the cursor if it's above the currently selected level.
2030 		 * The 2nd part of the if is an attempt to display it anyway when we eg. climb a hill.
2031 		 * But there are too many situations inside buildings that match the criteria (eg. actorclip around chair).
2032 		 * So disabled for now.*/
2033 		if (mousePos[2] > cl_worldlevel->integer/* && !RT_AllCellsBelowAreFilled(cl.mapData->map, fieldSize, pos)*/)
2034 			return;
2035 
2036 		/* Display Move-cursor. */
2037 		CL_AddTargetingBox(mousePos, false);
2038 
2039 		if (selActor->actorMode == M_PEND_MOVE) {
2040 			/* Also display a box for the pending move if we have one. */
2041 			CL_AddTargetingBox(selActor->mousePendPos, true);
2042 			if (!CL_ActorTraceMove(selActor->mousePendPos))
2043 				CL_ActorSetMode(selActor, M_MOVE);
2044 		}
2045 		break;
2046 	case M_FIRE_R:
2047 	case M_FIRE_L:
2048 		if (!selActor->fd)
2049 			return;
2050 
2051 		if (!selActor->fd->gravity)
2052 			CL_TargetingStraight(selActor->pos, selActor->fieldSize, mousePos);
2053 		else
2054 			CL_TargetingGrenade(selActor->pos, selActor->fieldSize, mousePos);
2055 		break;
2056 	case M_PEND_FIRE_R:
2057 	case M_PEND_FIRE_L:
2058 		if (!selActor->fd)
2059 			return;
2060 
2061 		/* Draw cursor at mousepointer */
2062 		CL_AddTargetingBox(mousePos, false);
2063 
2064 		/* Draw (pending) Cursor at target */
2065 		CL_AddTargetingBox(selActor->mousePendPos, true);
2066 
2067 		if (!selActor->fd->gravity)
2068 			CL_TargetingStraight(selActor->pos, selActor->fieldSize, selActor->mousePendPos);
2069 		else
2070 			CL_TargetingGrenade(selActor->pos, selActor->fieldSize, selActor->mousePendPos);
2071 		break;
2072 	default:
2073 		break;
2074 	}
2075 }
2076 
2077 static const vec3_t boxShift = { PLAYER_WIDTH, PLAYER_WIDTH, UNIT_HEIGHT / 2 - DIST_EPSILON };
2078 
2079 /**
2080  * @brief create a targeting box at the given position
2081  * @sa CL_ParseClientinfo
2082  */
CL_AddPathingBox(pos3_t pos,bool addUnreachableCells)2083 static bool CL_AddPathingBox (pos3_t pos, bool addUnreachableCells)
2084 {
2085 	const int TUneed = CL_ActorMoveLength(selActor, pos);
2086 	const int TUhave = CL_ActorUsableTUs(selActor);
2087 	if (!addUnreachableCells && TUhave < TUneed)
2088 		return false;
2089 
2090 	entity_t ent(RF_PATH);
2091 
2092 	Grid_PosToVec(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), pos, ent.origin);
2093 	VectorSubtract(ent.origin, boxShift, ent.origin);
2094 
2095 	int base; /* The floor relative to this cell */
2096 	base = Grid_Floor(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), pos);
2097 
2098 	/* Paint the box green if it is reachable,
2099 	 * yellow if it can be entered but is too far,
2100 	 * or red if it cannot be entered ever. */
2101 	if (base < -QuantToModel(PATHFINDING_MAX_FALL)) {
2102 		VectorSet(ent.color, 0.0, 0.0, 0.0); /* Can't enter - black */
2103 	} else {
2104 		/* Can reach - green
2105 		 * Passable but unreachable - yellow
2106 		 * Not passable - red */
2107 		VectorSet(ent.color, (TUneed > TUhave), (TUneed != ROUTING_NOT_REACHABLE), 0);
2108 	}
2109 
2110 	/* Set the box height to the ceiling value of the cell. */
2111 	int height; /* The total opening size */
2112 	height = 2 + std::min(TUneed * (UNIT_HEIGHT - 2) / ROUTING_NOT_REACHABLE, 16);
2113 	ent.oldorigin[2] = height;
2114 	ent.oldorigin[0] = TUneed;
2115 	ent.oldorigin[1] = TUhave;
2116 
2117 	ent.alpha = 0.25;
2118 
2119 	/* add it */
2120 	R_AddEntity(&ent);
2121 	return true;
2122 }
2123 
2124 /**
2125  * @brief Adds a pathing marker to the current floor when we render the world.
2126  * @sa CL_ViewRender
2127  * Draws the tracer (red, yellow, green box) on the grid
2128  */
CL_AddPathing(void)2129 void CL_AddPathing (void)
2130 {
2131 	pos3_t pos;
2132 
2133 	if (selActor == nullptr) {
2134 		return;
2135 	}
2136 
2137 	pos[2] = cl_worldlevel->integer;
2138 	for (pos[1] = std::max(mousePos[1] - 8, 0); pos[1] <= std::min(mousePos[1] + 8, PATHFINDING_WIDTH - 1); pos[1]++) {
2139 		for (pos[0] = std::max(mousePos[0] - 8, 0); pos[0] <= std::min(mousePos[0] + 8, PATHFINDING_WIDTH - 1); pos[0]++) {
2140 			CL_AddPathingBox(pos, true);
2141 		}
2142 	}
2143 }
2144 
2145 /**
2146  * @brief Adds an actor pathing marker to the current floor when we render the world.
2147  * @sa CL_ViewRender
2148  * Draws the tracer (red, yellow, green box) on the grid
2149  */
CL_AddActorPathing(void)2150 void CL_AddActorPathing (void)
2151 {
2152 	int x, y;
2153 	pos3_t pos;
2154 	int i = 0;
2155 
2156 	if (selActor == nullptr) {
2157 		return;
2158 	}
2159 
2160 	pos[2] = cl_worldlevel->integer;
2161 	for (y = 0; y <= PATHFINDING_WIDTH; y++) {
2162 		for (x = 0; x <= PATHFINDING_WIDTH; x++) {
2163 			pos[0] = (pos_t)x;
2164 			pos[1] = (pos_t)y;
2165 			i += CL_AddPathingBox(pos, false);
2166 			if (i > 1024)
2167 				return;
2168 		}
2169 	}
2170 }
2171 
2172 /**
2173  * @brief Plays various sounds on actor action.
2174  * @param[in] le The actor
2175  * @param[in] soundType Type of action (among actorSound_t) for which we need a sound.
2176  */
CL_ActorPlaySound(const le_t * le,actorSound_t soundType)2177 void CL_ActorPlaySound (const le_t* le, actorSound_t soundType)
2178 {
2179 	const char* actorSound = Com_GetActorSound(le->teamDef, le->gender, soundType);
2180 	if (actorSound) {
2181 		if (S_LoadAndPlaySample(actorSound, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_DEFAULT)) {
2182 			Com_DPrintf(DEBUG_SOUND|DEBUG_CLIENT, "CL_PlayActorSound: ActorSound: '%s'\n", actorSound);
2183 		}
2184 	}
2185 }
2186 
2187 /**
2188  * @brief create an arrow between from and to with the specified color ratios
2189  */
CL_AddArrow(vec3_t from,vec3_t to,float red,float green,float blue)2190 static void CL_AddArrow (vec3_t from, vec3_t to, float red, float green, float blue)
2191 {
2192 	/* Com_Printf("Adding arrow (%f, %f, %f) to (%f, %f, %f).\n", from[0], from[1], from[2], to[0], to[1], to[2]); */
2193 
2194 	entity_t ent(RF_ARROW);
2195 
2196 	VectorCopy(from, ent.origin);
2197 	VectorCopy(to, ent.oldorigin);
2198 	VectorSet(ent.color, red, green, blue);
2199 
2200 	ent.alpha = 0.25;
2201 
2202 	/* add it */
2203 	R_AddEntity(&ent);
2204 }
2205 
2206 /**
2207  * @brief Useful for debugging pathfinding
2208  */
CL_DisplayFloorArrows(void)2209 void CL_DisplayFloorArrows (void)
2210 {
2211 	vec3_t base, start;
2212 
2213 	Grid_PosToVec(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), truePos, base);
2214 	VectorCopy(base, start);
2215 	base[2] -= QUANT;
2216 	start[2] += QUANT;
2217 	CL_AddArrow(base, start, 0.0, 0.0, 0.0);
2218 }
2219 
2220 /**
2221  * @brief Useful for debugging pathfinding
2222  */
CL_DisplayObstructionArrows(void)2223 void CL_DisplayObstructionArrows (void)
2224 {
2225 	vec3_t base, start;
2226 
2227 	Grid_PosToVec(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), truePos, base);
2228 	VectorCopy(base, start);
2229 	CL_AddArrow(base, start, 0.0, 0.0, 0.0);
2230 }
2231 
2232 #ifdef DEBUG
2233 /**
2234  * @brief Triggers @c Step::isPossible in every direction at the current truePos.
2235  */
CL_DumpMoveMark_f(void)2236 static void CL_DumpMoveMark_f (void)
2237 {
2238 	const int temp = developer->integer;
2239 
2240 	if (!selActor)
2241 		return;
2242 
2243 	CL_BuildForbiddenList();
2244 	Grid_CalcPathing(cl.mapData->routing, ACTOR_GET_FIELDSIZE(selActor), &cl.pathMap, truePos, MAX_ROUTE_TUS, forbiddenList, forbiddenListLength);
2245 
2246 	CL_ActorConditionalMoveCalc(selActor);
2247 	developer->integer = temp;
2248 }
2249 
2250 /**
2251  * @brief Shows a table of the TUs that would be used by the current actor to move
2252  * relative to its current location
2253  */
CL_DumpTUs_f(void)2254 static void CL_DumpTUs_f (void)
2255 {
2256 	int x, y, crouchingState;
2257 	pos3_t pos, loc;
2258 
2259 	if (!selActor)
2260 		return;
2261 
2262 	crouchingState = LE_IsCrouched(selActor) ? 1 : 0;
2263 	VectorCopy(selActor->pos, pos);
2264 
2265 	Com_Printf("TUs around (%i, %i, %i).\n", pos[0], pos[1], pos[2]);
2266 
2267 	for (y = std::max(0, pos[1] - 8); y <= std::min(PATHFINDING_WIDTH, pos[1] + 8); y++) {
2268 		for (x = std::max(0, pos[0] - 8); x <= std::min(PATHFINDING_WIDTH, pos[0] + 8); x++) {
2269 			VectorSet(loc, x, y, pos[2]);
2270 			Com_Printf("%3i ", Grid_MoveLength(&cl.pathMap, loc, crouchingState, false));
2271 		}
2272 		Com_Printf("\n");
2273 	}
2274 	Com_Printf("TUs at (%i, %i, %i) = %i\n", pos[0], pos[1], pos[2], Grid_MoveLength(&cl.pathMap, pos, crouchingState, false));
2275 }
2276 
CL_DebugPath_f(void)2277 static void CL_DebugPath_f (void)
2278 {
2279 	const actorSizeEnum_t actorSize = ACTOR_SIZE_NORMAL;
2280 	const pos_t x = mousePos[0];
2281 	const pos_t y = mousePos[1];
2282 	const pos_t z = mousePos[2];
2283 
2284 	if (IN_GetMouseSpace() != MS_WORLD)
2285 		return;
2286 
2287 #if 0
2288 	int dir = 3;
2289 	RT_DebugSpecial(cl.mapTiles, cl.mapData->routing, actorSize, x, y, dir, cl.leInlineModelList);
2290 
2291 	bool found = Grid_FindPath(cl.mapData->routing, actorSize, &cl.pathMap, selActor->pos, mousePos, 0, 600, nullptr, nullptr);
2292 	if (found)
2293 		Com_Printf("found the path !\n");
2294 	{
2295 //	pos3_t boxmin = {134,128,0};
2296 //	pos3_t boxmax = {136,130,1};
2297 //	GridBox myBox(boxmin, boxmax);
2298 //	Grid_RecalcBoxRouting(cl.mapTiles, cl.mapData->routing, myBox, cl.leInlineModelList);
2299 	}
2300 #endif
2301 
2302 	RT_DebugPathDisplay(cl.mapData->routing, actorSize, x, y, z);
2303 
2304 	GridBox mbox(cl.mapData->mapBox);
2305 	int xW = mbox.maxs[0] - mbox.mins[0];
2306 	int yW = mbox.maxs[1] - mbox.mins[1];
2307 	int zW = mbox.maxs[2] - mbox.mins[2];
2308 	Com_Printf("Statistics:\nWorldsize(x/y/z) %i/%i/%i\n", xW, yW, zW);
2309 	int numCells = xW * yW * zW;
2310 	Com_Printf("number of Cells: %i\n", numCells);
2311 	Com_Printf("Base Coords (x/y/z) %i/%i/%i\n", mbox.mins[0], mbox.mins[1], mbox.mins[2]);
2312 }
2313 #endif
2314 
2315 
2316 /**
2317  * @brief Switch to the next living soldier
2318  */
CL_ActorNext_f(void)2319 static void CL_ActorNext_f (void)
2320 {
2321 	if (CL_BattlescapeRunning()) {
2322 		CL_ActorSelectNext();
2323 	}
2324 }
2325 
2326 /**
2327  * @brief Switch to the previous living soldier
2328  */
CL_ActorPrev_f(void)2329 static void CL_ActorPrev_f (void)
2330 {
2331 	if (CL_BattlescapeRunning()) {
2332 		CL_ActorSelectPrev();
2333 	}
2334 }
2335 
2336 /**
2337  * @brief Selects a soldier while we are on battlescape
2338  */
CL_ActorSelect_f(void)2339 static void CL_ActorSelect_f (void)
2340 {
2341 	/* check syntax */
2342 	if (Cmd_Argc() < 2) {
2343 		Com_Printf("Usage: %s <num>\n", Cmd_Argv(0));
2344 		return;
2345 	}
2346 
2347 	/* check whether we are connected (tactical mission) */
2348 	if (CL_BattlescapeRunning()) {
2349 		const int num = atoi(Cmd_Argv(1));
2350 		CL_ActorSelectList(num);
2351 	}
2352 }
2353 
2354 /**
2355  * @brief Update the skin of the current soldier
2356  */
CL_ActorUpdate_f(void)2357 static void CL_ActorUpdate_f (void)
2358 {
2359 	const int num = cl_selected->integer;
2360 
2361 	/* We are in the base or multiplayer inventory */
2362 	int i = 0;
2363 	LIST_Foreach(chrDisplayList, character_t, chr) {
2364 		if (i++ != num)
2365 			continue;
2366 		CL_UpdateCharacterValues(chr);
2367 	}
2368 }
2369 
2370 /**
2371  * @sa G_ActorVis
2372  * @param[in] le The local entity to do the check for
2373  * @param[in] check The local entity to check the visibility for
2374  * @return @c true if the given edict is visible from the given world coordinate, @c false otherwise.
2375  */
CL_ActorVis(const le_t * le,const le_t * check)2376 static bool CL_ActorVis (const le_t* le, const le_t* check)
2377 {
2378 	vec3_t test, dir;
2379 	float delta;
2380 	int i;
2381 	vec3_t from;
2382 
2383 	VectorCopy(le->origin, from);
2384 
2385 	/* start on eye height */
2386 	VectorCopy(check->origin, test);
2387 	if (LE_IsDead(check)) {
2388 		test[2] += PLAYER_DEAD;
2389 		delta = 0;
2390 	} else if (LE_IsCrouched(check)) {
2391 		test[2] += PLAYER_CROUCH - 2;
2392 		delta = (PLAYER_CROUCH - PLAYER_MIN) / 2 - 2;
2393 	} else {
2394 		test[2] += PLAYER_STAND;
2395 		delta = (PLAYER_STAND - PLAYER_MIN) / 2 - 2;
2396 	}
2397 
2398 	/* side shifting -> better checks */
2399 	dir[0] = from[1] - check->origin[1];
2400 	dir[1] = check->origin[0] - from[0];
2401 	dir[2] = 0;
2402 	VectorNormalize(dir);
2403 	VectorMA(test, -7, dir, test);
2404 
2405 	/* do 3 tests */
2406 	for (i = 0; i < 3; i++) {
2407 		const trace_t tr = CL_Trace(from, test, AABB(), le, nullptr, MASK_SOLID, cl_worldlevel->integer);
2408 		/* trace didn't reach the target - something was hit before */
2409 		if (tr.fraction < 1.0) {
2410 			/* look further down or stop */
2411 			if (!delta)
2412 				return false;
2413 			VectorMA(test, 7, dir, test);
2414 			test[2] -= delta;
2415 			continue;
2416 		}
2417 
2418 		return true;
2419 	}
2420 
2421 	return false;
2422 }
2423 
2424 /**
2425  * @brief Cycles between visible (to selected actor) aliens.
2426  * @sa CL_NextAlien_f
2427  */
CL_NextAlienVisibleFromActor_f(void)2428 static void CL_NextAlienVisibleFromActor_f (void)
2429 {
2430 	static int lastAlien = 0;
2431 	int i;
2432 
2433 	if (!selActor)
2434 		return;
2435 
2436 	if (lastAlien >= cl.numLEs)
2437 		lastAlien = 0;
2438 
2439 	i = lastAlien;
2440 	do {
2441 		const le_t* le;
2442 		if (++i >= cl.numLEs)
2443 			i = 0;
2444 		le = &cl.LEs[i];
2445 		if (le->inuse && LE_IsLivingAndVisibleActor(le) && le->team != cls.team
2446 		 && !LE_IsCivilian(le)) {
2447 			if (CL_ActorVis(selActor, le)) {
2448 				lastAlien = i;
2449 				CL_ViewCenterAtGridPosition(le->pos);
2450 				CL_ParticleSpawn("fadeTracer", 0, selActor->origin, le->origin);
2451 				return;
2452 			}
2453 		}
2454 	} while (i != lastAlien);
2455 }
2456 
2457 /**
2458  * @brief Cycles between visible aliens
2459  * @sa CL_NextAlienVisibleFromActor_f
2460  */
CL_NextAlien_f(void)2461 static void CL_NextAlien_f (void)
2462 {
2463 	int lastAlien;
2464 	int i;
2465 
2466 	if (cl.numLEs <= 0)
2467 		return;
2468 
2469 	lastAlien = Cvar_GetInteger("ui_lastalien");
2470 	lastAlien = std::max(0, std::min(cl.numLEs - 1, lastAlien));
2471 
2472 	i = lastAlien;
2473 	do {
2474 		const le_t* le;
2475 		if (++i >= cl.numLEs)
2476 			i = 0;
2477 		le = &cl.LEs[i];
2478 		if (le->inuse && LE_IsLivingAndVisibleActor(le) && le->team != cls.team
2479 		 && le->team != TEAM_CIVILIAN) {
2480 			lastAlien = i;
2481 			CL_ViewCenterAtGridPosition(le->pos);
2482 			Cvar_SetValue("ui_lastalien", lastAlien);
2483 			return;
2484 		}
2485 	} while (i != lastAlien);
2486 }
2487 
2488 /**
2489  * @brief Cycles between visible aliens in reverse direction
2490  * @sa CL_NextAlienVisibleFromActor_f
2491  */
CL_PrevAlien_f(void)2492 static void CL_PrevAlien_f (void)
2493 {
2494 	int lastAlien;
2495 	int i;
2496 
2497 	if (cl.numLEs <= 0)
2498 		return;
2499 
2500 	lastAlien = Cvar_GetInteger("ui_lastalien");
2501 	lastAlien = std::max(0, std::min(cl.numLEs - 1, lastAlien));
2502 
2503 	i = lastAlien;
2504 	do {
2505 		const le_t* le;
2506 		if (--i < 0)
2507 			i = cl.numLEs - 1;
2508 		le = &cl.LEs[i];
2509 		if (le->inuse && LE_IsLivingAndVisibleActor(le) && le->team != cls.team
2510 		 && le->team != TEAM_CIVILIAN) {
2511 			lastAlien = i;
2512 			CL_ViewCenterAtGridPosition(le->pos);
2513 			Cvar_SetValue("ui_lastalien", lastAlien);
2514 			return;
2515 		}
2516 	} while (i != lastAlien);
2517 }
2518 
2519 /**
2520  * Performs pending actions for the given actor
2521  * @param le The actor that should perform the pending actions
2522  */
CL_ActorConfirmAction(le_t * le)2523 static void CL_ActorConfirmAction (le_t* le)
2524 {
2525 	if (le->team != cl.actTeam)
2526 		return;
2527 
2528 	/* might be a friendly player controlled actor */
2529 	if (le->pnum != cl.pnum)
2530 		return;
2531 
2532 	switch (le->actorMode) {
2533 	case M_PEND_MOVE:
2534 		CL_ActorStartMove(le, le->mousePendPos);
2535 		break;
2536 	case M_PEND_FIRE_R:
2537 	case M_PEND_FIRE_L:
2538 		CL_ActorShoot(le, le->mousePendPos);
2539 		break;
2540 	default:
2541 		break;
2542 	}
2543 }
2544 
2545 /**
2546  * @brief Executes "pending" actions such as walking and firing.
2547  * @note Manually triggered by the player when hitting the "confirm" button.
2548  * @note When triggering this twice in 1000ms all pending actions are performed, otherwise only
2549  * the current selected actor is handled.
2550  */
CL_ActorConfirmAction_f(void)2551 static void CL_ActorConfirmAction_f (void)
2552 {
2553 	static int time = 0;
2554 
2555 	if (time - cl.time < 1000) {
2556 		le_t* le = nullptr;
2557 		while ((le = LE_GetNextInUse(le))) {
2558 			if (LE_IsLivingActor(le) && !LE_IsStunned(le) && le->team == cls.team)
2559 				CL_ActorConfirmAction(le);
2560 		}
2561 	} else {
2562 		time = cl.time;
2563 		if (!selActor)
2564 			return;
2565 		CL_ActorConfirmAction(selActor);
2566 	}
2567 }
2568 
ACTOR_InitStartup(void)2569 void ACTOR_InitStartup (void)
2570 {
2571 	cl_autostand = Cvar_Get("cl_autostand","1", CVAR_USERINFO | CVAR_ARCHIVE, "Prevent accidental wasting of TUs by allowing the actor to automatically stand up before starting long walks.");
2572 	confirm_actions = Cvar_Get("confirm_actions", "0", CVAR_ARCHIVE, "Confirm all actions in tactical mode");
2573 	cl_showactors = Cvar_Get("cl_showactors", "1", 0, "Show actors on the battlefield");
2574 	Cmd_AddCommand("actor_next", CL_ActorNext_f, N_("Toggle to next living actor"));
2575 	Cmd_AddCommand("actor_prev", CL_ActorPrev_f, N_("Toggle to previous living actor"));
2576 	Cmd_AddCommand("actor_select", CL_ActorSelect_f, N_("Select an actor from list"));
2577 	Cmd_AddCommand("actor_updatecurrent", CL_ActorUpdate_f, N_("Update an actor"));
2578 	Cmd_AddCommand("actor_standcrouch", CL_ActorStandCrouch_f, N_("Toggle stand/crouch."));
2579 	Cmd_AddCommand("actor_useheadgear", CL_ActorUseHeadgear_f, N_("Toggle the headgear"));
2580 	Cmd_AddCommand("actor_use", CL_ActorUse_f, N_("Use"));
2581 	Cmd_AddCommand("actor_confirmaction", CL_ActorConfirmAction_f, N_("Confirm the current action"));
2582 	Cmd_AddCommand("actor_nextalien", CL_NextAlienVisibleFromActor_f, N_("Toggle to the next alien in sight of the selected actor."));
2583 
2584 	Cmd_AddCommand("nextalien", CL_NextAlien_f, N_("Toggle camera to the next visible alien."));
2585 	Cmd_AddCommand("prevalien", CL_PrevAlien_f, N_("Toggle camera to the previous visible alien."));
2586 
2587 #ifdef DEBUG
2588 	Cmd_AddCommand("debug_path", CL_DebugPath_f, "Display routing data for current mouse position.");
2589 	Cmd_AddCommand("debug_drawblocked", CL_DisplayBlockedPaths_f, "Draw a marker for all blocked map-positions.");
2590 	Cmd_AddCommand("debug_movemark", CL_DumpMoveMark_f, "Trigger Step::isPossible in every direction at the current truePos.");
2591 	Cmd_AddCommand("debug_tus", CL_DumpTUs_f, "Show a table of the TUs that would be used by the current actor to move relative to his current location.");
2592 	Cmd_AddCommand("debug_actorinvlist", nullptr, "Show the inventory list of all actors.");
2593 #endif /* DEBUG */
2594 }
2595