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