1 /**
2 * @file
3 */
4
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23 */
24
25 #include "g_actor.h"
26 #include "g_client.h"
27 #include "g_edicts.h"
28 #include "g_health.h"
29 #include "g_inventory.h"
30 #include "g_reaction.h"
31 #include "g_spawn.h"
32 #include "g_utils.h"
33 #include "g_vis.h"
34
35 /* however we manipulate TUs, this value must never be exceeded */
36 #define MAX_TU (ROUTING_NOT_REACHABLE - 1)
37
38 /**
39 * @brief Checks whether the given edict is a living actor
40 * @param[in] ent The edict to perform the check for
41 * @sa LE_IsLivingActor
42 */
G_IsLivingActor(const Edict * ent)43 bool G_IsLivingActor (const Edict* ent)
44 {
45 return G_IsActor(ent) && (G_IsStunned(ent) || !G_IsDead(ent));
46 }
47
48 /**
49 * @brief Make the actor use (as in open/close) a door edict
50 * @note Will also check whether the door is still reachable (this might have
51 * changed due to the rotation) after the usage
52 * @param actor The actor that is using the door
53 * @param door The door that should be opened/closed
54 */
G_ActorUseDoor(Edict * actor,Edict * door)55 void G_ActorUseDoor (Edict* actor, Edict* door)
56 {
57 /* check whether it's part of an edict group but not the master */
58 if (door->flags & FL_GROUPSLAVE)
59 door = door->groupMaster;
60
61 if (!G_ClientUseEdict(actor->getPlayer(), actor, door))
62 return;
63
64 /* end this loop here, for the AI this is a) not interesting,
65 * and b) could result in endless loops */
66 if (G_IsAI(actor))
67 return;
68
69 Edict* closeActor = nullptr;
70 while ((closeActor = G_FindRadius(closeActor, door->origin, UNIT_SIZE * 3))) {
71 /* check whether the door is still reachable (this might have
72 * changed due to the rotation) or whether an actor can reach it now */
73 G_TouchTriggers(closeActor);
74 }
75 }
76
77 /**
78 * @brief Checks whether the given actor is currently standing in a rescue zone
79 * @param[in] actor The actor to check
80 * @return @c true if the actor is standing in a rescue zone, @c false otherwise.
81 */
G_ActorIsInRescueZone(const Edict * actor)82 bool G_ActorIsInRescueZone (const Edict* actor)
83 {
84 return actor->inRescueZone;
85 }
86
87 /**
88 * @brief Set the rescue zone data
89 * @param[out] actor The actor to set the rescue zone flag for
90 * @param[in] inRescueZone @c true if the actor is in the rescue zone, @c false otherwise
91 */
G_ActorSetInRescueZone(Edict * actor,bool inRescueZone)92 void G_ActorSetInRescueZone (Edict* actor, bool inRescueZone)
93 {
94 if (inRescueZone == G_ActorIsInRescueZone(actor))
95 return;
96
97 actor->inRescueZone = inRescueZone;
98 }
99
100 /**
101 * @brief Handles the client actions (interaction with the world)
102 * @param actor The actors' edict
103 * @param ent The edict that can be used by the actor
104 */
G_ActorSetClientAction(Edict * actor,Edict * ent)105 void G_ActorSetClientAction (Edict* actor, Edict* ent)
106 {
107 if (actor->clientAction == ent)
108 return;
109
110 assert(ent == nullptr || (ent->flags & FL_CLIENTACTION));
111 actor->clientAction = ent;
112 if (ent == nullptr) {
113 G_EventResetClientAction(*actor);
114 } else {
115 G_EventSetClientAction(*actor);
116 }
117 }
118
119 /**
120 * @brief Calculates the amount of all currently reserved TUs
121 * @param ent The actor to calculate the reserved TUs for
122 * @return The amount of reserved TUs for the given actor edict
123 */
G_ActorGetReservedTUs(const Edict * ent)124 int G_ActorGetReservedTUs (const Edict* ent)
125 {
126 const chrReservations_t* res = &ent->chr.reservedTus;
127 return res->reaction + res->shot + res->crouch;
128 }
129
130 /**
131 * @brief Calculates the amount of usable TUs. This is without the reserved TUs.
132 * @param[in] ent The actor to calculate the amount of usable TUs for. If @c ent is @c nullptr, we
133 * return zero here
134 * @return The amount of usable TUs for the given actor edict
135 */
G_ActorUsableTUs(const Edict * ent)136 int G_ActorUsableTUs (const Edict* ent)
137 {
138 if (!ent)
139 return 0;
140
141 return ent->TU - G_ActorGetReservedTUs(ent);
142 }
143
144 /**
145 * @brief Calculates the amount of TUs that are needed for the current selected reaction fire mode.
146 * @note It's assumed that there is a sane fire mode selected for reaction fire
147 * @param[in] ent The actors edict
148 * @return The amount of TUs that are needed for the current selected reaction fire mode.
149 */
G_ActorGetTUForReactionFire(const Edict * ent)150 int G_ActorGetTUForReactionFire (const Edict* ent)
151 {
152 const FiremodeSettings *fm = &ent->chr.RFmode;
153
154 const Item* weapon = ent->getHandItem(fm->getHand());
155 assert(weapon);
156 assert(weapon->def());
157
158 const fireDef_t* fd = weapon->getFiredefs();
159 assert(fd);
160 return G_ActorGetModifiedTimeForFiredef(ent, &fd[fm->getFmIdx()], false);
161 }
162
163 /**
164 * @brief Reserves TUs for different actor actions
165 * @param[in,out] ent The actor to reserve TUs for. Might not be @c nullptr.
166 * @param[in] resReaction TUs for reaction fire
167 * @param[in] resShot TUs for shooting
168 * @param[in] resCrouch TUs for going into crouch mode
169 */
G_ActorReserveTUs(Edict * ent,int resReaction,int resShot,int resCrouch)170 void G_ActorReserveTUs (Edict* ent, int resReaction, int resShot, int resCrouch)
171 {
172 if (ent->TU >= resReaction + resShot + resCrouch) {
173 ent->chr.reservedTus.reaction = resReaction;
174 ent->chr.reservedTus.shot = resShot;
175 ent->chr.reservedTus.crouch = resCrouch;
176 }
177
178 G_EventActorSendReservations(*ent);
179 }
180
181 /**
182 * @brief Turns an actor around
183 * @param[in] ent the actor (edict) we are talking about
184 * @param[in] dir the direction to turn the edict into, might be an action
185 * @return Bitmask of visible (VIS_*) values
186 * @sa G_CheckVisTeamAll
187 */
G_ActorDoTurn(Edict * ent,byte dir)188 int G_ActorDoTurn (Edict* ent, byte dir)
189 {
190 float angleDiv;
191 const byte* rot;
192 int i, num;
193 int status;
194
195 assert(ent->dir < CORE_DIRECTIONS);
196 assert(dir < PATHFINDING_DIRECTIONS);
197
198 /*
199 * If dir is at least CORE_DIRECTIONS but less than FLYING_DIRECTIONS,
200 * then the direction is an action.
201 */
202 /** @todo If performing an action, ensure the actor is facing the direction
203 * needed to perform the action if needed (climbing ladders).
204 */
205 if (dir >= CORE_DIRECTIONS && dir < FLYING_DIRECTIONS)
206 return 0;
207
208 /* Clamp dir between 0 and CORE_DIRECTIONS - 1. */
209 dir &= (CORE_DIRECTIONS - 1);
210 assert(dir < CORE_DIRECTIONS);
211
212 /* Return if no rotation needs to be done. */
213 if (ent->dir == dir)
214 return 0;
215
216 /* calculate angle difference */
217 angleDiv = directionAngles[dir] - directionAngles[ent->dir];
218 if (angleDiv > 180.0)
219 angleDiv -= 360.0;
220 if (angleDiv < -180.0)
221 angleDiv += 360.0;
222
223 /* prepare rotation - decide whether the actor turns around the left
224 * shoulder or the right - this is needed the get the rotation vector
225 * that is used below to check in each of the rotation steps
226 * (1/8, 22.5 degree) whether something became visible while turning */
227 if (angleDiv > 0) {
228 const int angleStep = (360.0 / CORE_DIRECTIONS);
229 rot = dvleft;
230 num = (angleDiv + angleStep / 2) / angleStep;
231 } else {
232 const int angleStep = (360.0 / CORE_DIRECTIONS);
233 rot = dvright;
234 num = (-angleDiv + angleStep / 2) / angleStep;
235 }
236
237 /* do rotation and vis checks */
238 status = 0;
239
240 /* check every angle (1/8 steps - on the way to the end direction) in the rotation
241 * whether something becomes visible and stop before reaching the final direction
242 * if this happened */
243 for (i = 0; i < num; i++) {
244 ent->dir = rot[ent->dir];
245 assert(ent->dir < CORE_DIRECTIONS);
246 status |= G_CheckVisTeamAll(ent->team, 0, ent);
247 }
248
249 if (status & VIS_STOP) {
250 /* send the turn */
251 G_EventActorTurn(*ent);
252 }
253
254 return status;
255 }
256
257 /**
258 * @brief Sets correct bounding box for actor (state dependent).
259 * @param[in] ent Pointer to entity for which bounding box is being set.
260 * @note Also re-links the actor edict - because the server must know about the
261 * changed bounding box for tracing to work.
262 */
G_ActorSetMaxs(Edict * ent)263 void G_ActorSetMaxs (Edict* ent)
264 {
265 if (G_IsCrouched(ent))
266 VectorSet(ent->maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_CROUCH);
267 else if (G_IsDead(ent) && !CHRSH_IsTeamDefRobot(ent->chr.teamDef))
268 VectorSet(ent->maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_DEAD);
269 else
270 VectorSet(ent->maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_STAND);
271
272 /* Link it. */
273 gi.LinkEdict(ent);
274 }
275
G_ActorCalculateMaxTU(const Edict * ent)276 int G_ActorCalculateMaxTU (const Edict* ent)
277 {
278 const int invWeight = ent->chr.inv.getWeight();
279 const int currentMaxTU = GET_TU(ent->chr.score.skills[ABILITY_SPEED], GET_ENCUMBRANCE_PENALTY(invWeight,
280 ent->chr.score.skills[ABILITY_POWER])) * G_ActorGetInjuryPenalty(ent, MODIFIER_TU);
281
282 return std::min(currentMaxTU, MAX_TU);
283 }
284
285 /**
286 * @brief Set time units for the given edict. Based on speed skills
287 * @param ent The actor edict
288 */
G_ActorGiveTimeUnits(Edict * ent)289 void G_ActorGiveTimeUnits (Edict* ent)
290 {
291 const int tus = G_IsDazed(ent) ? 0 : G_ActorCalculateMaxTU(ent);
292 G_ActorSetTU(ent, tus);
293 G_RemoveDazed(ent);
294 }
295
G_ActorSetTU(Edict * ent,int tus)296 void G_ActorSetTU (Edict* ent, int tus)
297 {
298 if (tus > 0 && tus < ent->TU) {
299 if (g_notu != nullptr && g_notu->integer) {
300 ent->TU = G_ActorCalculateMaxTU(ent);
301 return;
302 }
303 }
304 ent->TU = std::max(tus, 0);
305 }
306
G_ActorUseTU(Edict * ent,int tus)307 void G_ActorUseTU (Edict* ent, int tus)
308 {
309 G_ActorSetTU(ent, ent->TU - tus);
310 }
311
G_ActorStun(Edict * ent,const Edict * attacker)312 static bool G_ActorStun (Edict* ent, const Edict* attacker)
313 {
314 /* already dead or stunned? */
315 if (G_IsDead(ent))
316 return false;
317
318 /* no other state should be set here */
319 ent->state = STATE_STUN;
320 G_ActorSetMaxs(ent);
321 ent->link = attacker;
322
323 G_ActorModifyCounters(attacker, ent, -1, 0, 1);
324
325 return true;
326 }
327
G_ActorModifyCounters(const Edict * attacker,const Edict * victim,int deltaAlive,int deltaKills,int deltaStuns)328 void G_ActorModifyCounters (const Edict* attacker, const Edict* victim, int deltaAlive, int deltaKills, int deltaStuns)
329 {
330 const int spawned = level.num_spawned[victim->team];
331 const int attackerTeam = (attacker != nullptr ? attacker->team : MAX_TEAMS);
332 byte* alive = level.num_alive;
333
334 alive[victim->team] += deltaAlive;
335 if (alive[victim->team] > spawned)
336 gi.Error("alive counter out of sync");
337
338 if (deltaStuns != 0) {
339 byte* stuns = level.num_stuns[attackerTeam];
340 stuns[victim->team] += deltaStuns;
341 if (stuns[victim->team] > spawned)
342 gi.Error("stuns counter out of sync");
343 }
344
345 if (deltaKills != 0) {
346 byte* kills = level.num_kills[attackerTeam];
347 kills[victim->team] += deltaKills;
348 if (kills[victim->team] > spawned)
349 gi.Error("kills counter out of sync");
350 }
351 #if 0
352 {
353 int i;
354
355 Com_Printf("num_spawned: %i\n", spawned);
356
357 if (attacker)
358 for (i = 0; i < MAX_TEAMS; i++) {
359 int j;
360
361 if (i == victim->team) {
362 Com_Printf("^2num_alive (team %i): %i\n", i, level.num_alive[i]);
363 } else {
364 Com_Printf("num_alive (team %i): %i\n", i, level.num_alive[i]);
365 }
366
367 for (j = 0; j < MAX_TEAMS; j++) {
368 if (j == victim->team) {
369 Com_Printf("^2num_kills (team %i killed %i): %i\n", i, j, level.num_kills[i][j]);
370 Com_Printf("^2num_stuns (team %i stunned %i): %i\n", i, j, level.num_stuns[i][j]);
371 } else if (i == attacker->team) {
372 Com_Printf("^3num_kills (team %i killed %i): %i\n", i, j, level.num_kills[i][j]);
373 Com_Printf("^3num_stuns (team %i stunned %i): %i\n", i, j, level.num_stuns[i][j]);
374 } else {
375 Com_Printf("num_kills (team %i killed %i): %i\n", i, j, level.num_kills[i][j]);
376 Com_Printf("num_stuns (team %i stunned %i): %i\n", i, j, level.num_stuns[i][j]);
377 }
378 }
379 }
380 }
381 #endif
382 }
383
384 /**
385 * @brief Fills a vector with the eye position of a given actor
386 * @param[in] actor The actor edict to get the eye position in the world from
387 * @param[out] eye The eye vector world position
388 */
G_ActorGetEyeVector(const Edict * actor,vec3_t eye)389 void G_ActorGetEyeVector (const Edict* actor, vec3_t eye)
390 {
391 VectorCopy(actor->origin, eye);
392 if (G_IsCrouched(actor) || G_IsPanicked(actor))
393 eye[2] += EYE_CROUCH;
394 else
395 eye[2] += EYE_STAND;
396 }
397
G_ActorRevitalise(Edict * ent)398 static void G_ActorRevitalise (Edict* ent)
399 {
400 if (G_IsStunned(ent)) {
401 G_RemoveStunned(ent);
402 /** @todo have a look at the morale value of
403 * the ent and maybe get into rage or panic? */
404 G_ActorModifyCounters(ent->link, ent, 1, 0, -1);
405 G_GetFloorItems(ent);
406 }
407 G_ActorSetMaxs(ent);
408
409 /* check if the player appears/perishes, seen from other teams */
410 G_CheckVis(ent);
411
412 /* calc new vis for this player */
413 G_CheckVisTeamAll(ent->team, 0, ent);
414
415 G_PrintStats("%s is revitalized.", ent->chr.name);
416 }
417
G_ActorCheckRevitalise(Edict * ent)418 void G_ActorCheckRevitalise (Edict* ent)
419 {
420 if (G_IsStunned(ent) && ent->STUN < ent->HP) {
421 /* check that we could move after we stood up */
422 Edict* otherActor = nullptr;
423 while ((otherActor = G_EdictsGetNextInUse(otherActor))) {
424 if (!VectorCompare(ent->pos, otherActor->pos))
425 continue;
426 if (G_IsBlockingMovementActor(otherActor))
427 return;
428 }
429
430 G_ActorRevitalise(ent);
431 G_EventActorRevitalise(*ent);
432 G_EventActorStateChange(~G_VisToPM(ent->visflags), *ent);
433 G_SendStats(*ent);
434 }
435 }
436
G_ActorDie(Edict * ent,const Edict * attacker)437 static bool G_ActorDie (Edict* ent, const Edict* attacker)
438 {
439 const bool stunned = G_IsStunned(ent);
440
441 G_RemoveStunned(ent);
442
443 if (G_IsDead(ent))
444 return false;
445
446 G_SetState(ent, 1 + rand() % MAX_DEATH);
447 G_ActorSetMaxs(ent);
448
449 if (stunned) {
450 G_ActorModifyCounters(attacker, ent, 0, 1, 0);
451 G_ActorModifyCounters(ent->link, ent, 0, 0, -1);
452 } else {
453 G_ActorModifyCounters(attacker, ent, -1, 1, 0);
454 }
455
456 return true;
457 }
458
459 /**
460 * @brief Reports and handles death or stun of an actor. If the HP of an actor is zero the actor
461 * will die, otherwise the actor will get stunned.
462 * @param[in] ent Pointer to an entity being killed or stunned actor.
463 * @param[in] attacker Pointer to attacker - it must be notified about state of victim.
464 * @todo Discuss whether stunned actor should really drop everything to floor. Maybe
465 * it should drop only what he has in hands? Stunned actor can wake later during mission.
466 */
G_ActorDieOrStun(Edict * ent,Edict * attacker)467 bool G_ActorDieOrStun (Edict* ent, Edict* attacker)
468 {
469 bool state;
470
471 if (ent->HP == 0)
472 state = G_ActorDie(ent, attacker);
473 else
474 state = G_ActorStun(ent, attacker);
475
476 /* no state change performed? */
477 if (!state) {
478 gi.DPrintf("G_ActorDieOrStun: State wasn't changed\n");
479 return false;
480 }
481
482 if (!G_IsStunned(ent))
483 ent->solid = SOLID_NOT;
484
485 /* send death */
486 G_EventActorDie(*ent, attacker != nullptr);
487 G_EventActorStateChange(~G_VisToPM(ent->visflags), *ent);
488
489 /* handle inventory - drop everything but the armour to the floor */
490 G_InventoryToFloor(ent);
491 G_ClientStateChange(ent->getPlayer(), ent, ~STATE_REACTION, false);
492
493 /* check if the player appears/perishes, seen from other teams */
494 G_CheckVis(ent);
495
496 /* check if the attacker appears/perishes, seen from other teams */
497 if (attacker)
498 G_CheckVis(attacker);
499
500 /* calc new vis for this player */
501 G_CheckVisTeamAll(ent->team, 0, attacker);
502
503 /* unlink the floor container */
504 ent->resetFloor();
505
506 G_ReactionFireOnDead(ent);
507
508 return true;
509 }
510
511 /**
512 * @brief Get the content flags from where the actor is currently standing
513 */
G_ActorGetContentFlags(const vec3_t origin)514 int G_ActorGetContentFlags (const vec3_t origin)
515 {
516 vec3_t pointTrace;
517
518 VectorCopy(origin, pointTrace);
519 pointTrace[2] += PLAYER_MIN;
520
521 return gi.PointContents(pointTrace);
522 }
523
524 /**
525 * @brief Moves an item inside an inventory. Floors are handled special.
526 * @param[in] actor The pointer to the selected/used edict/soldier.
527 * @param[in] fromContType The container (-id) the item should be moved from.
528 * @param[in] fItem The item you want to move.
529 * @param[in] toContType The container (-def) the item should be moved to.
530 * @param[in] tx x position where you want the item to go in the destination container
531 * @param[in] ty y position where you want the item to go in the destination container
532 * @param[in] checkaction Set this to true if you want to check for TUs, otherwise false.
533 * @sa event PA_INVMOVE
534 * @sa AI_ActorThink
535 */
G_ActorInvMove(Edict * actor,const invDef_t * fromContType,Item * fItem,const invDef_t * toContType,int tx,int ty,bool checkaction)536 bool G_ActorInvMove (Edict* actor, const invDef_t* fromContType, Item* fItem, const invDef_t* toContType, int tx, int ty, bool checkaction)
537 {
538 Edict* floor;
539 bool newFloor;
540 Item* tc;
541 playermask_t mask;
542 inventory_action_t ia;
543 Item fromItemBackup, toItemBackup;
544 int fx, fy;
545 int originalTU, reservedTU = 0;
546 Player &player = actor->getPlayer();
547
548 assert(fItem);
549 assert(fItem->def());
550
551 /* Store the location/item of 'from' BEFORE actually moving items with moveInInventory. */
552 fromItemBackup = *fItem;
553
554 /* Store the location of 'to' BEFORE actually moving items with moveInInventory
555 * so in case we swap ammo the client can be updated correctly */
556 tc = actor->chr.inv.getItemAtPos(toContType, tx, ty);
557 if (tc)
558 toItemBackup = *tc;
559 else
560 toItemBackup = *fItem;
561
562 /* Get first used bit in item. */
563 fItem->getFirstShapePosition(&fx, &fy);
564 fx += fItem->getX();
565 fy += fItem->getY();
566
567 /* Check if action is possible */
568 /* TUs are 1 here - but this is only a dummy - the real TU check is done in the inventory functions below */
569 if (checkaction && !G_ActionCheckForCurrentTeam(player, actor, 1))
570 return false;
571
572 if (!actor->chr.inv.canHoldItemWeight(fromContType->id, toContType->id, *fItem, actor->chr.score.skills[ABILITY_POWER])) {
573 G_ClientPrintf(player, PRINT_HUD, _("This soldier can not carry anything else."));
574 return false;
575 }
576
577 /* "get floor ready" - searching for existing floor-edict */
578 floor = G_GetFloorItems(actor);
579 if (toContType->isFloorDef() && !floor) {
580 /* We are moving to the floor, but no existing edict for this floor-tile found -> create new one */
581 floor = G_SpawnFloor(actor->pos);
582 newFloor = true;
583 } else if (fromContType->isFloorDef() && !floor) {
584 /* We are moving from the floor, but no existing edict for this floor-tile found -> this should never be the case. */
585 gi.DPrintf("G_ClientInvMove: No source-floor found.\n");
586 return false;
587 } else {
588 /* There already exists an edict for this floor-tile. */
589 newFloor = false;
590 }
591
592 /* search for space */
593 Item* item2;
594 if (tx == NONE) {
595 item2 = actor->chr.inv.getItemAtPos(fromContType, fItem->getX(), fItem->getY());
596 if (item2)
597 actor->chr.inv.findSpace(toContType, item2, &tx, &ty, fItem);
598 if (tx == NONE)
599 return false;
600 }
601
602 /** @todo what if we don't have enough TUs after subtracting the reserved ones? */
603 /* Because moveInInventory don't know anything about character_t and it updates actor->TU,
604 * we need to save original actor->TU for the sake of checking TU reservations. */
605 originalTU = actor->TU;
606 reservedTU = G_ActorGetReservedTUs(actor);
607 /* Temporary decrease actor->TU to make moveInInventory do what expected. */
608 G_ActorUseTU(actor, reservedTU);
609 /* Try to actually move the item and check the return value after restoring valid actor->TU. */
610 ia = game.i.moveInInventory(&actor->chr.inv, fromContType, fItem, toContType, tx, ty, checkaction ? &actor->TU : nullptr, &item2);
611 /* Now restore the original actor->TU and decrease it for TU used for inventory move. */
612 G_ActorSetTU(actor, originalTU - (originalTU - reservedTU - actor->TU));
613
614 switch (ia) {
615 case IA_NONE:
616 /* No action possible - abort */
617 return false;
618 case IA_NOTIME:
619 G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not enough TUs!"));
620 return false;
621 case IA_NORELOAD:
622 G_ClientPrintf(player, PRINT_HUD,
623 _("Can't perform action - weapon already fully loaded with the same ammunition!"));
624 return false;
625 default:
626 /* Continue below. */
627 break;
628 }
629
630 /* successful inventory change; remove the item in clients */
631 if (fromContType->isFloorDef()) {
632 /* We removed an item from the floor - check how the client
633 * needs to be updated. */
634 assert(!newFloor);
635 if (actor->getFloor()) {
636 /* There is still something on the floor. */
637 floor->setFloor(actor);
638 /* Delay this if swapping ammo, otherwise the le will be removed in the client before we can add back
639 * the current ammo because removeNextFrame is set in LE_PlaceItem() if the floor le has no items */
640 if (ia != IA_RELOAD_SWAP)
641 G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy);
642 } else {
643 /* Floor is empty, remove the edict (from server + client) if we are
644 * not moving to it. */
645 if (!toContType->isFloorDef()) {
646 G_EventPerish(*floor);
647 G_FreeEdict(floor);
648 } else
649 G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy);
650 }
651 } else {
652 G_EventInventoryDelete(*actor, G_TeamToPM(actor->team), fromContType->id, fx, fy);
653 }
654
655 /* send tu's */
656 G_SendStats(*actor);
657
658 assert(item2);
659 Item item = *item2;
660
661 if (ia == IA_RELOAD || ia == IA_RELOAD_SWAP) {
662 /* reload */
663 if (toContType->isFloorDef())
664 mask = G_VisToPM(floor->visflags);
665 else
666 mask = G_TeamToPM(actor->team);
667
668 G_EventInventoryReload(toContType->isFloorDef() ? *floor : *actor, mask, &item, toContType, item2);
669
670 if (ia == IA_RELOAD) {
671 return true;
672 } else { /* ia == IA_RELOAD_SWAP */
673 item.setAmmoLeft(NONE_AMMO);
674 item.setAmmoDef(nullptr);
675 item.setDef(toItemBackup.ammoDef());
676 item.rotated = fromItemBackup.rotated;
677 item.setAmount(toItemBackup.getAmount());
678 toContType = fromContType;
679 if (toContType->isFloorDef()) {
680 /* moveInInventory placed the swapped ammo in an available space, check where it was placed
681 * so we can place it at the same place in the client, otherwise since fItem hasn't been removed
682 * this could end in a different place in the client - will cause an error if trying to use it again */
683 item2 = actor->chr.inv.findInContainer(toContType->id, &item);
684 assert(item2);
685 fromItemBackup = item;
686 fromItemBackup.setX(item2->getX());
687 fromItemBackup.setY(item2->getY());
688 }
689 tx = fromItemBackup.getX();
690 ty = fromItemBackup.getY();
691 }
692 }
693
694 /* We moved an item to the floor - check how the client needs to be updated. */
695 if (toContType->isFloorDef()) {
696 /* we have to link the temp floor container to the new floor edict or add
697 * the item to an already existing floor edict - the floor container that
698 * is already linked might be from a different entity (this might happen
699 * in case of a throw by another actor) */
700 floor->setFloor(actor);
701
702 /* A new container was created for the floor. */
703 if (newFloor) {
704 /* Send item info to the clients */
705 G_CheckVis(floor);
706 } else {
707 /* use the backup item to use the old amount values, because the clients have to use the same actions
708 * on the original amount. Otherwise they would end in a different amount of items as the server (+1) */
709 G_EventInventoryAdd(*floor, G_VisToPM(floor->visflags), 1);
710 G_WriteItem(fromItemBackup, toContType->id, tx, ty);
711 G_EventEnd();
712 /* Couldn't remove it before because that would remove the le from the client and would cause battlescape to crash
713 * when trying to add back the swapped ammo above */
714 if (ia == IA_RELOAD_SWAP)
715 G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy);
716 }
717 } else {
718 G_EventInventoryAdd(*actor, G_TeamToPM(actor->team), 1);
719 G_WriteItem(item, toContType->id, tx, ty);
720 G_EventEnd();
721 }
722
723 G_ReactionFireSettingsUpdate(actor, actor->chr.RFmode.getFmIdx(), actor->chr.RFmode.getHand(), actor->chr.RFmode.getWeapon());
724
725 /* Other players receive weapon info only. */
726 mask = G_VisToPM(actor->visflags) & ~G_TeamToPM(actor->team);
727 if (mask) {
728 if (fromContType->isRightDef() || fromContType->isLeftDef()) {
729 G_EventInventoryDelete(*actor, mask, fromContType->id, fx, fy);
730 }
731 if (toContType->isRightDef() || toContType->isLeftDef()) {
732 G_EventInventoryAdd(*actor, mask, 1);
733 G_WriteItem(item, toContType->id, tx, ty);
734 G_EventEnd();
735 }
736 }
737
738 return true;
739 }
740
741 /**
742 * @brief Reload weapon with actor.
743 * @param[in] ent Pointer to an actor reloading weapon.
744 * @param[in] invDef Reloading weapon in right or left hand.
745 * @sa AI_ActorThink
746 */
G_ActorReload(Edict * ent,const invDef_t * invDef)747 void G_ActorReload (Edict* ent, const invDef_t* invDef)
748 {
749 const objDef_t* weapon;
750
751 if (ent->getContainer(invDef->id)) {
752 weapon = ent->getContainer(invDef->id)->def();
753 } else if (invDef->isLeftDef() && ent->getRightHandItem()->isHeldTwoHanded()) {
754 /* Check for two-handed weapon */
755 invDef = INVDEF(CID_RIGHT);
756 weapon = ent->getRightHandItem()->def();
757 } else
758 return;
759
760 assert(weapon);
761
762 /* LordHavoc: Check if item is researched when in singleplayer? I don't think this is really a
763 * cheat issue as in singleplayer there is no way to inject fake client commands in the virtual
764 * network buffer, and in multiplayer everything is researched */
765
766 /* search for clips and select the one that is available easily */
767 /* also try the temp containers */
768 const invDef_t* bestContainer = nullptr;
769 Item* ammoItem = nullptr;
770 int tu = 100;
771 const Container* cont = nullptr;
772 while ((cont = ent->chr.inv.getNextCont(cont, true))) {
773 if (cont->def()->out >= tu)
774 continue;
775 /* Once we've found at least one clip, there's no point
776 * searching other containers if it would take longer
777 * to retrieve the ammo from them than the one
778 * we've already found. */
779 Item* item = nullptr;
780 while ((item = cont->getNextItem(item))) {
781 if (item->def()->isLoadableInWeapon(weapon)) {
782 ammoItem = item;
783 bestContainer = INVDEF(cont->id);
784 tu = bestContainer->out;
785 break;
786 }
787 }
788 }
789
790 /* send request */
791 if (bestContainer)
792 G_ActorInvMove(ent, bestContainer, ammoItem, invDef, 0, 0, true);
793 }
794
G_ActorGetModifiedTimeForFiredef(const Edict * const ent,const fireDef_t * const fd,const bool reaction)795 int G_ActorGetModifiedTimeForFiredef (const Edict* const ent, const fireDef_t* const fd, const bool reaction)
796 {
797 return fd->time * G_ActorGetInjuryPenalty(ent, reaction ? MODIFIER_REACTION : MODIFIER_SHOOTING);
798 }
799