UFO: Alien Invasion
Loading...
Searching...
No Matches
g_ai.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24*/
25
26#include "g_ai.h"
27#include "g_actor.h"
28#include "g_client.h"
29#include "g_combat.h"
30#include "g_edicts.h"
31#include "g_health.h"
32#include "g_inventory.h"
33#include "g_move.h"
34#include "g_utils.h"
35#include "g_vis.h"
36#include "g_reaction.h"
37
38class AiAction {
39public:
43 byte shots;
45 const fireDef_t* fd;
46 int z_align;
47
48 inline void reset() {
49 OBJZERO(*this);
50 }
51};
52
59
63AiAreaSearch::AiAreaSearch(const pos3_t origin, int radius, bool flat) {
64 plotArea(origin, radius, flat);
65}
66
71 _area.clear();
72}
73
80 return _area.dequeue(pos);
81}
82
87
93 qnode_s* node = static_cast<qnode_s*>(G_TagMalloc(sizeof(qnode_s), TAG_LEVEL));
94 VectorCopy(data, node->data);
95 node->next = nullptr;
96
97 if (isEmpty())
98 _head = _tail = node;
99 else {
100 _tail->next = node;
101 _tail = node;
102 }
103 _count++;
104}
105
112 if (isEmpty())
113 return false;
114
115 VectorCopy(_head->data, data);
116 qnode_s* node = _head;
117 _head = _head->next;
118 _count--;
119 G_MemFree(node);
120
121 return true;
122}
123
128 qnode_s* next = nullptr;
129 for (qnode_s* node = _head; node; node = next) {
130 next = node->next;
131 G_MemFree(node);
132 }
133 _head = _tail = nullptr;
134 _count = 0;
135}
136
143void AiAreaSearch::plotArea(const pos3_t origin, int radius, bool flat) {
144 /* Starting with a sphere with radius 0 (just the center pos) out to the given radius */
145 for (int i = 0; i <= radius; ++i) {
146 /* Battlescape has max PATHFINDING_HEIGHT levels, so cap the z offset to the max levels
147 * the unit can currently go up or down */
148 const int zOfsMax = std::min(std::max(static_cast<int>(origin[2]), PATHFINDING_HEIGHT - origin[2] - 1), i);
149 /* Starting a the center z level up to zOfsMax levels */
150 if (flat)
152 else
153 for (int zOfs = 0; zOfs <= zOfsMax; ++zOfs) {
154 pos3_t center = {origin[0], origin[1], static_cast<pos_t>(origin[2] + zOfs)};
155 plotCircle(center, i - zOfs);
156 /* We need to cover both up and down directions, so we Flip the sign (if there's actually a z offset) */
157 if (zOfs != 0) {
158 center[2] = origin[2] - zOfs;
159 plotCircle(center, i - zOfs);
160 }
161 }
162 }
163}
164
165void AiAreaSearch::plotCircle(const pos3_t origin, int radius) {
166 /* There are always more levels in one direction than the other (since the number is even)
167 * so check if out of bounds */
168 if (origin[2] >= PATHFINDING_HEIGHT)
169 return;
170 /* This is the main loop of the midpoint circle algorithm this specific variation
171 * circles of consecutive integer radius won't overlap in any point or leave gaps in between,
172 * Coincidentally (at time of writing) a circle of radius n will be exactly the farthest a normal actor can walk
173 * with n * 2 TU, in ideal conditions (standing, no obstacles, no penalties) */
174 for (int dy = 1, xOfs = 0, yOfs = radius; xOfs <= yOfs; ++xOfs, dy += 1, yOfs = radius - (dy >> 1)) {
175 /* Have the offsets, now to fill the octants */
176 plotPos(origin, xOfs, yOfs);
177 /* Need to Flip the signs, for each one */
178 if (xOfs != 0) {
179 plotPos(origin, -xOfs, yOfs);
180 }
181 if (yOfs != 0) {
182 plotPos(origin, xOfs, -yOfs);
183 }
184 if (xOfs != 0 && yOfs != 0) {
185 plotPos(origin, -xOfs, -yOfs);
186 }
187 /* And need to Flip the x and y offsets too */
188 if (xOfs != yOfs) {
189 plotPos(origin, yOfs, xOfs);
190 if (yOfs != 0) {
191 plotPos(origin, -yOfs, xOfs);
192 }
193 if (xOfs != 0) {
194 plotPos(origin, yOfs, -xOfs);
195 }
196 if (yOfs != 0 && xOfs != 0) {
197 plotPos(origin, -yOfs, -xOfs);
198 }
199 }
200 }
201}
202
203void AiAreaSearch::plotPos(const pos3_t origin, int xOfs, int dy) {
204 /* There's more distance to one (possible) edge of the map than the other
205 * check if out of bounds to be safe */
206 if (origin[0] + xOfs < 0 || origin[0] + xOfs >= PATHFINDING_WIDTH || origin[1] + dy < 0
207 || origin[1] + dy >= PATHFINDING_WIDTH)
208 return;
209 const pos3_t pos = {static_cast<pos_t>(origin[0] + xOfs), static_cast<pos_t>(origin[1] + dy), origin[2]};
210 /* Most maps won't use the full space available so check against the actual map area */
211 vec3_t vec;
212 PosToVec(pos, vec);
213 if (!gi.isOnMap(vec))
214 return;
215 _area.enqueue(pos);
216}
217
218#define SCORE_HIDE 60
219#define SCORE_CLOSE_IN 20
220#define SCORE_KILL 30
221#define SCORE_RANDOM 10
222#define SCORE_REACTION_ERADICATION 30
223#define SCORE_REACTION_FEAR_FACTOR 20
224#define SCORE_NONHIDING_PLACE_PENALTY 25
225#define SCORE_RAGE 40
226#define SCORE_DAMAGE 100.0f
227#define SCORE_DAMAGE_FACTOR 1.25f
228#define SCORE_CIV_FACTOR 0.25f
229#define SCORE_DISABLED_FACTOR 0.25f
230#define SCORE_DAMAGE_WORTH_FACTOR 0.1f
231
232#define SCORE_CIV_RANDOM 10
233#define SCORE_RUN_AWAY 50
234#define SCORE_CIV_LAZINESS 5
235#define RUN_AWAY_DIST 160
236#define WAYPOINT_CIV_DIST 768
237#define HERD_THRESHOLD 128.0f
238#define SCORE_HERDING_PENALTY 100
239#define SCORE_NOSAFE_POSITION_PENALTY 500
240
241#define SCORE_MISSION_OPPONENT_TARGET 50
242#define SCORE_MISSION_TARGET 60
243#define SCORE_MISSION_HOLD 25
244#define MISSION_HOLD_DIST 96
245
246#define SCORE_PANIC_RUN_TO_FRIENDS 300.0f
247#define SCORE_PANIC_FLEE_FROM_STRANGERS 500.0f
248#define SCORE_PANIC_RANDOM 25.0f
249
250#define AI_ACTION_NOTHING_FOUND -10000.0f
251
252#define CLOSE_IN_DIST 1200.0f
253#define SPREAD_FACTOR 8.0f
254#define SPREAD_NORM(x) ((x) > 0 ? SPREAD_FACTOR/((x)*torad) : 0)
256#define HIDE_DIST 25
257#define HERD_DIST 25
258#define HOLD_DIST 3
259
260#define CALC_DAMAGE_SAMPLES 10.0f
261#define INVDEF_FOR_SHOOTTYPE(st) (IS_SHOT_RIGHT(st)?INVDEF(CID_RIGHT):IS_SHOT_LEFT(st)?INVDEF(CID_LEFT):IS_SHOT_HEADGEAR(st)?INVDEF(CID_HEADGEAR):nullptr)
262
265
266void AI_Init (void)
267{
268 hidePathingTable = nullptr;
269 herdPathingTable = nullptr;
270}
271
275bool AI_HasLineOfFire (const Actor* actor, const Edict* target)
276{
277 for (shoot_types_t shootType = ST_RIGHT; shootType < ST_NUM_SHOOT_TYPES; shootType++) {
278 const Item* item = AI_GetItemForShootType(shootType, actor);
279 if (item == nullptr)
280 continue;
281
282 const fireDef_t* fdArray = item->getFiredefs();
283 if (fdArray == nullptr)
284 continue;
285
286 for (fireDefIndex_t fdIdx = 0; fdIdx < item->ammoDef()->numFiredefs[fdArray->weapFdsIdx]; fdIdx++) {
287 const fireDef_t* fd = &fdArray[fdIdx];
288 if (AI_CheckLineOfFire(actor, target, fd, 1))
289 return true;
290 }
291 }
292 return false;
293}
294
298static bool AI_IsExposed (int team, Actor* check)
299{
300 if (G_TestVis(team, check, VT_PERISHCHK | VT_NOFRUSTUM) & VS_YES)
301 return true;
302
303 Actor* from = nullptr;
304 while ((from = G_EdictsGetNextLivingActor(from))) {
305 const int fromTeam = from->getTeam();
306 if ((team >= 0 && fromTeam != team) || (team < 0 && fromTeam == -team))
307 continue;
308
309 if (AI_HasLineOfFire(from, check))
310 return true;
311 }
312
313 return false;
314}
315
322static bool AI_CheckFF (const Edict* ent, const vec3_t target, float spread, float radius)
323{
324 /* spread data */
325 spread *= 2;
326 if (spread < 1.0)
327 spread = 1.0;
328 spread *= torad;
329
330 const float cosSpread = cos(spread);
331 vec3_t dtarget;
332 VectorSubtract(target, ent->origin, dtarget);
333 VectorNormalizeFast(dtarget);
334 vec3_t back;
335 VectorScale(dtarget, PLAYER_WIDTH / spread, back);
336
337 Actor* check = nullptr;
338 while ((check = G_EdictsGetNextLivingActorOfTeam(check, ent->getTeam()))) {
339 if (!ent->isSameAs(check)) {
340 vec3_t dcheck;
341 /* found ally */
342 VectorSubtract(check->origin, ent->origin, dcheck);
343 if (DotProduct(dtarget, dcheck) > 0.0) {
344 /* ally in front of player */
345 VectorAdd(dcheck, back, dcheck);
346 VectorNormalizeFast(dcheck);
347 if (DotProduct(dtarget, dcheck) > cosSpread)
348 return true;
349 }
350 }
351 if (VectorDist(target, check->origin) < radius + UNIT_SIZE)
352 return true;
353 }
354
355 /* no ally in danger */
356 return false;
357}
358
364bool AI_FighterCheckShoot (const Actor* actor, const Edict* check, const fireDef_t* fd, float dist)
365{
366 /* check range */
367 if (dist > fd->range)
368 return false;
369
370 /* if insane, we don't check more */
371 if (actor->isInsane())
372 return true;
373
374 /* don't shoot - we are too close */
375 if (dist < fd->splrad)
376 return false;
377
378 /* check FF */
379 vec2_t effSpread;
380 G_CalcEffectiveSpread(actor, fd, effSpread);
381 if (AI_CheckFF(actor, check->origin, effSpread[0], fd->splrad))
382 return false;
383
384 return true;
385}
386
396bool AI_CheckUsingDoor (const Edict* ent, const Edict* door)
397{
398 /* don't try to use the door in every case */
399 if (frand() < 0.3f)
400 return false;
401
402 /* not in the view frustum - don't use the door while not seeing it */
403 if (!G_FrustumVis(door, ent->origin))
404 return false;
405
406 /* if the alien is trying to hide and the door is
407 * still opened, close it */
408 if (ent->hiding && door->doorState == STATE_OPENED)
409 return true;
410
411 /* aliens and civilians need different handling */
412 switch (ent->getTeam()) {
413 case TEAM_ALIEN: {
414 /* only use the door when there is no civilian or phalanx to kill */
415 Actor* check = nullptr;
416
417 /* see if there are enemies */
418 while ((check = G_EdictsGetNextLivingActor(check))) {
419 /* don't check for aliens */
420 if (check->isSameTeamAs(ent))
421 continue;
422 /* check whether the origin of the enemy is inside the
423 * AI actors view frustum */
424 if (!G_FrustumVis(check, ent->origin))
425 continue;
426 /* check whether the enemy is close enough to change the state */
427 if (VectorDist(check->origin, ent->origin) > G_VisCheckDist(ent))
428 continue;
429 const float actorVis = G_ActorVis(check, ent, true);
430 /* there is a visible enemy, don't use that door */
431 if (actorVis > ACTOR_VIS_0)
432 return false;
433 }
434 }
435 break;
436 case TEAM_CIVILIAN:
437 /* don't use any door if no alien is inside the viewing angle - but
438 * try to hide behind the door when there is an alien */
439 break;
440 default:
441 gi.DPrintf("Invalid team in AI_CheckUsingDoor: %i for ent type: %i\n", ent->getTeam(), ent->type);
442 break;
443 }
444 return true;
445}
446
452static bool AI_CheckCrouch (const Actor* actor)
453{
454 if (G_IsCrouched(actor))
455 return false;
456
457 Actor* check = nullptr;
458
459 /* see if we are very well visible by an enemy */
460 while ((check = G_EdictsGetNextLivingActor(check))) {
461 /* don't check for civilians or aliens */
462 if (check->isSameTeamAs(actor) || G_IsCivilian(check))
463 continue;
464 /* check whether the origin of the enemy is inside the
465 * AI actors view frustum */
466 if (!G_FrustumVis(check, actor->origin))
467 continue;
468 /* check whether the enemy is close enough to change the state */
469 if (VectorDist(check->origin, actor->origin) > G_VisCheckDist(actor))
470 continue;
471 const float actorVis = G_ActorVis(check, actor, true);
472 if (actorVis >= ACTOR_VIS_50)
473 return true;
474 }
475 return false;
476}
477
484bool AI_HideNeeded (const Actor* actor)
485{
486 /* aliens will consider hiding if they are not brave, or there is a dangerous enemy in sight */
487 const bool brave = actor->morale > mor_brave->integer;
488
489 Actor* from = nullptr;
490 /* test if actor is visible */
491 while ((from = G_EdictsGetNextLivingActor(from))) {
492 if (!AI_IsHostile(actor, from))
493 continue;
494
495 const Item* item = from->getRightHandItem();
496 if (!item)
497 item = from->getLeftHandItem();
498 if (!item)
499 continue;
500
501 const fireDef_t* fd = item->getFiredefs();
502 /* search the (visible) inventory (by just checking the weapon in the hands of the enemy) */
503 const bool inRange = fd != nullptr && fd->range * fd->range >= VectorDistSqr(actor->origin, from->origin);
504 const int damageRand = !inRange ? 0 : fd->damage[0] + fd->spldmg[0] + ((fd->damage[1] + fd->spldmg[1]) * crand());
505 const int damage = std::max(0, damageRand);
506 if (!brave || damage >= actor->HP / 3) {
507 const int hidingTeam = AI_GetHidingTeam(actor);
508 /* now check whether this enemy is visible for this alien */
509 if (G_Vis(hidingTeam, actor, from, VT_NOFRUSTUM) || AI_HasLineOfFire(from, actor))
510 return true;
511 }
512 }
513 return false;
514}
515
522static inline const Item* AI_GetItemFromInventory (const Item* ic)
523{
524 if (ic == nullptr)
525 return nullptr;
526
527 const Item* item = ic;
528 if (item->ammoDef() && item->isWeapon() && !item->mustReload())
529 return item;
530
531 return nullptr;
532}
533
541const Item* AI_GetItemForShootType (shoot_types_t shootType, const Edict* ent)
542{
543 /* optimization: reaction fire is automatic */
544 if (IS_SHOT_REACTION(shootType))
545 return nullptr;
546
547 /* check that the current selected shoot type also has a valid item in its
548 * corresponding hand slot of the inventory. */
549 if (IS_SHOT_RIGHT(shootType)) {
550 const Item* item = ent->getRightHandItem();
551 return AI_GetItemFromInventory(item);
552 } else if (IS_SHOT_LEFT(shootType)) {
553 const Item* item = ent->getLeftHandItem();
554 return AI_GetItemFromInventory(item);
555 } else if (IS_SHOT_HEADGEAR(shootType)) {
556 return nullptr;
557 }
558
559 return nullptr;
560}
561
570int AI_GetHidingTeam (const Edict* ent)
571{
572 if (G_IsCivilian(ent))
573 return TEAM_ALIEN;
574 return -ent->getTeam();
575}
576
581bool AI_CheckPosition (const Actor* const actor, const pos3_t pos)
582{
583 if (actor->isInsane())
584 return true;
585
586 /* Don't stand on hurt triggers or fire/stun gas */
587 Edict* check = nullptr;
588 while ((check = G_EdictsGetNextInUse(check))) {
589 if (!check->isSamePosAs(pos) || check->dmg <= 0)
590 continue;
591 if (G_ApplyProtection(actor, check->dmgtype, check->dmg) > 0)
592 return false;
593 }
594 return true;
595}
596
606bool AI_FindHidingLocation (int team, Actor* actor, const pos3_t from, int tuLeft)
607{
608 /* We need a local table to calculate the hiding steps */
609 if (!hidePathingTable)
611
612 /* search hiding spot */
613 const int maxTUs = std::min(tuLeft, HIDE_DIST * 2);
614 const int distance = (maxTUs + 1) / TU_MOVE_STRAIGHT;
615 G_MoveCalcLocal(hidePathingTable, 0, actor, from, maxTUs);
616
617 int bestScore = AI_ACTION_NOTHING_FOUND;
618 pos3_t bestPos = {0, 0, PATHFINDING_HEIGHT};
619 AiAreaSearch searchArea(from, distance, true);
620 while (searchArea.getNext(actor->pos)) {
621 /* Don't have TUs to walk there */
622 const pos_t delta = G_ActorMoveLength(actor, hidePathingTable, actor->pos, false);
623 if (delta > tuLeft || delta == ROUTING_NOT_REACHABLE)
624 continue;
625
626 /* Don't stand on dangerous terrain! */
627 if (!AI_CheckPosition(actor, actor->pos))
628 continue;
629
630 /* If enemies see this position, it doesn't qualify as hiding spot */
631 actor->calcOrigin();
632 if (AI_IsExposed(team, actor))
633 continue;
634
635 const int score = tuLeft - delta;
636 if (score > bestScore) {
637 bestScore = score;
638 VectorCopy(actor->pos, bestPos);
639 }
640 }
641
642 if (bestPos[2] != PATHFINDING_HEIGHT) {
643 VectorCopy(bestPos, actor->pos);
644 return true;
645 }
646 return false;
647}
648
659bool AI_FindHerdLocation (Actor* actor, const pos3_t from, const vec3_t target, int tu, bool inverse)
660{
661 if (!herdPathingTable)
663
664 /* find the nearest enemy actor to the target*/
665 vec_t bestLength = -1.0f;
666 Actor* next = nullptr;
667 Actor* enemy = nullptr;
668 int team = AI_GetHidingTeam(actor);
669 const bool invTeam = team < 0;
670 team = std::abs(team);
671 while ((next = G_EdictsGetNextLivingActor(next))) {
672 if (next->getTeam() == team ? invTeam : !invTeam)
673 continue;
674 const vec_t length = VectorDistSqr(target, next->origin);
675 if (length < bestLength || bestLength < 0.0f) {
676 enemy = next;
677 bestLength = length;
678 }
679 }
680 if (!enemy)
681 return false;
682
683 /* calculate move table */
684 const int maxTUs = std::min(tu, HERD_DIST * 2);
685 const int distance = (maxTUs + 1) / TU_MOVE_STRAIGHT;
686 G_MoveCalcLocal(herdPathingTable, 0, actor, from, maxTUs);
687
688 /* search the location */
689 pos3_t bestPos = {0, 0, PATHFINDING_HEIGHT};
690 bestLength = VectorDistSqr(target, actor->origin);
691 AiAreaSearch searchArea(from, distance, true);
692 while (searchArea.getNext(actor->pos)) {
693 /* time */
694 const pos_t delta = G_ActorMoveLength(actor, herdPathingTable, actor->pos, false);
695 if (delta > tu || delta == ROUTING_NOT_REACHABLE)
696 continue;
697
698 /* Don't stand on dangerous terrain! */
699 if (!AI_CheckPosition(actor, actor->pos))
700 continue;
701
702 actor->calcOrigin();
703 const vec_t length = VectorDistSqr(actor->origin, target);
704 /* Don't pack them too close */
706 continue;
707
708 if (length < bestLength || bestPos[2] == PATHFINDING_HEIGHT) {
709 vec3_t vfriend, venemy;
710 /* check this position to locate behind target from enemy */
711 VectorSubtract(target, actor->origin, vfriend);
712 VectorNormalizeFast(vfriend);
713 VectorSubtract(enemy->origin, actor->origin, venemy);
714 VectorNormalizeFast(venemy);
715 const float dotProd = DotProduct(vfriend, venemy);
716 if ((inverse ? -dotProd : dotProd) > 0.5f) {
717 bestLength = length;
718 VectorCopy(actor->pos, bestPos);
719 }
720 }
721 }
722
723 if (bestPos[2] != PATHFINDING_HEIGHT) {
724 VectorCopy(bestPos, actor->pos);
725 return true;
726 }
727
728 return false;
729}
730
737static Edict* AI_SearchDestroyableObject (const Actor* actor, const fireDef_t* fd)
738{
739#if 0
740 /* search best none human target */
741 Edict* check = nullptr;
742 float dist;
743
744 while ((check = G_EdictsGetNextInUse(check))) {
745 if (G_IsBreakable(check)) {
746 if (!AI_FighterCheckShoot(actor, check, fd, &dist))
747 continue;
748
749 /* check whether target is visible enough */
750 const float vis = G_ActorVis(actor->origin, check, true);
751 if (vis < ACTOR_VIS_0)
752 continue;
753
754 /* take the first best breakable or door and try to shoot it */
755 return check;
756 }
757 }
758#endif
759 return nullptr;
760}
761
762#define LOF_CHECK_PARTITIONS 4
763bool AI_CheckLineOfFire (const Actor* shooter, const Edict* target, const fireDef_t* fd, int shots)
764{
765 vec3_t dir, origin;
766 VectorSubtract(target->origin, shooter->origin, dir);
767 fd->getShotOrigin(shooter->origin, dir, shooter->isCrouched(), origin);
768 if (!fd->gravity) {
769 /* gun-to-target line free? */
770 const trace_t trace = G_Trace(Line(origin, target->origin), shooter, MASK_SHOT);
771 const Edict* trEnt = G_EdictsGetByNum(trace.entNum);
772 const bool hitBreakable = trEnt && G_IsBrushModel(trEnt) && G_IsBreakable(trEnt);
773 const bool shotBreakable = hitBreakable && (fd->shots > 1 || shots > 1)
774 && trEnt->HP < fd->damage[0] && !(fd->splrad > 0.0f) && !fd->bounce;
775 if (trace.fraction < 1.0f && (!trEnt || (!VectorCompare(trEnt->pos, target->pos) && !shotBreakable)))
776 return false;
777 } else {
778 /* gun-to-target *parabola* free? */
779 vec3_t at, v;
780 VectorCopy(target->origin, at);
781 /* Grenades are targeted at the ground in G_ShootGrenade */
782 at[2] -= GROUND_DELTA;
783 const float dt = gi.GrenadeTarget(origin, at, fd->range, fd->launched, fd->rolled, v) / LOF_CHECK_PARTITIONS;
784 if (!dt)
785 return false;
786 VectorSubtract(at, origin, dir);
787 VectorScale(dir, 1.0f / LOF_CHECK_PARTITIONS, dir);
788 dir[2] = 0;
789 float vz = v[2];
790 int i;
791 for (i = 0; i < LOF_CHECK_PARTITIONS; i++) {
792 VectorAdd(origin, dir, at);
793 at[2] += dt * (vz - 0.5f * GRAVITY * dt);
794 vz -= GRAVITY * dt;
795 const trace_t trace = G_Trace(Line(origin, at), shooter, MASK_SHOT);
796 const Edict* trEnt = G_EdictsGetByNum(trace.entNum);
797 if (trace.fraction < 1.0f && (!trEnt || !VectorCompare(trEnt->pos, target->pos))) {
798 break;
799 }
800 VectorCopy(at, origin);
801 }
803 return false;
804 }
805 return true;
806}
807
811float AI_CalcShotDamage (Actor* actor, const Actor* target, const fireDef_t* fd, shoot_types_t shotType)
812{
813 const int shots = ceil(CALC_DAMAGE_SAMPLES / fd->shots);
814 const int zAlign = !fd->gravity && (fd->splrad > 0.0f || target->isStunned()) ? GROUND_DELTA : 0;
815 shot_mock_t mock;
816 for (int i = 0; i < shots; ++i)
817 G_ClientShoot(actor->getPlayer(), actor, target->pos, shotType, fd->fdIdx, &mock, false, zAlign);
818
819 if (mock.damage == 0)
820 return 0.0f;
821
822 const int totalCount = mock.enemyCount + mock.friendCount + mock.civilian;
823 const int eCount = totalCount - ((actor->isInsane() ? 0 : mock.friendCount)
824 + (G_IsAlien(actor) || actor->isInsane() ? 0 : mock.civilian));
825 return mock.damage * (static_cast<float>(eCount) / totalCount) / shots;
826}
827
831static void AI_FindBestFiredef (AiAction* aia, Actor* actor, Actor* check, const Item* item, shoot_types_t shootType, int tu, float* maxDmg, int* bestTime, const fireDef_t* fdArray)
832{
833 bool hasLineOfFire = false;
834 int shotChecked = NONE;
835
836 const float dist = VectorDist(actor->origin, check->origin);
837 for (fireDefIndex_t fdIdx = 0; fdIdx < item->ammoDef()->numFiredefs[fdArray->weapFdsIdx]; fdIdx++) {
838 const fireDef_t* fd = &fdArray[fdIdx];
839 const int time = G_ActorGetModifiedTimeForFiredef(actor, fd, false);
840 /* how many shoots can this actor do */
841 const int shots = tu / time;
842 if (shots) {
843 const bool stunWeapon = (item->def()->dmgtype == gi.csi->damStunElectro || item->def()->dmgtype == gi.csi->damStunGas);
844 if (stunWeapon && !actor->isInsane() && (check->isStunned() || CHRSH_IsTeamDefRobot(check->chr.teamDef)))
845 return;
846
847 if (!AI_FighterCheckShoot(actor, check, fd, dist))
848 continue;
849
850 /*
851 * check weapon can hit, we only want to do this once unless the LoF actually changes
852 * between shots, only hand grenades seem to do this (rolled vs thrown)
853 */
854 const int shotFlags = fd->gravity | (fd->launched << 1) | (fd->rolled << 2);
855 if (shotChecked != shotFlags) {
856 shotChecked = shotFlags;
857 hasLineOfFire = AI_CheckLineOfFire(actor, check, fd, shots);
858 }
859 if (!hasLineOfFire)
860 continue;
861
862 /* calculate expected damage */
863 float dmg = AI_CalcShotDamage(actor, check, fd, shootType) * shots;
864
865 /* It is said that there is no kill like overkill but... */
866 dmg = std::min(dmg, check->HP * SCORE_DAMAGE_FACTOR) * SCORE_DAMAGE / check->HP;
867
868 if (dmg > check->HP && check->isReaction())
869 /* reaction shooters eradication bonus */
871 else if (dmg > check->HP)
872 /* standard kill bonus */
873 dmg = check->HP + SCORE_KILL;
874
875 /* ammo is limited and shooting gives away your position */
876 if (dmg < check->HP * SCORE_DAMAGE_WORTH_FACTOR)
877 continue;
878
879 /* Reaction fire malus */
880 if (!actor->isInsane() && check->isReaction() && G_ActorVis(check, actor, true) > ACTOR_VIS_0)
882
883 /* civilian malus */
884 if (G_IsCivilian(check) && !actor->isRaged())
885 dmg *= SCORE_CIV_FACTOR;
886
887 /* Stunned malus */
888 if (check->isStunned() && !actor->isRaged())
890
891 /* add random effects */
892 if (dmg > 0)
893 dmg += SCORE_RANDOM * frand();
894
895 /* check if most damage can be done here */
896 if (dmg > *maxDmg) {
897 *maxDmg = dmg;
898 *bestTime = time * shots;
899 aia->shootType = shootType;
900 aia->shots = shots;
901 aia->target = check;
902 aia->fd = fd;
903 if (!fd->gravity && (fd->splrad > 0.0f || check->isStunned()))
904 aia->z_align = GROUND_DELTA;
905 else
906 aia->z_align = 0;
907 }
908
909 if (!aia->target) {
910 aia->target = AI_SearchDestroyableObject(actor, fd);
911 if (aia->target) {
912 /* don't take vis into account, don't multiply with amount of shots
913 * others (human victims) should be preferred, that's why we don't
914 * want a too high value here */
915 *maxDmg = (fd->damage[0] + fd->spldmg[0]);
916 *bestTime = time * shots;
917 aia->shootType = shootType;
918 aia->shots = shots;
919 aia->fd = fd;
920 }
921 }
922 }
923 }
924}
925
933bool AI_IsHostile (const Actor* actor, const Edict* target)
934{
935 if (actor == target)
936 return false;
937
938 if (actor->isInsane())
939 return true;
940
941 if (!target->isOpponent(actor))
942 return false;
943
944 /* don't shoot civs in multiplayer */
945 if (G_IsMultiPlayer())
946 return !G_IsCivilian(target);
947
948 return true;
949}
950
954const invDef_t* AI_SearchGrenade (const Actor* actor, Item** ip)
955{
956 /* search for grenades and select the one that is available easily */
957 const Container* cont = nullptr;
958 const invDef_t* bestContainer = nullptr;
959 Item* weapon = nullptr;
960 int cost = 100;
961 while ((cont = actor->chr.inv.getNextCont(cont, true))) {
962 if (cont->def()->out >= cost)
963 continue;
964 Item* item = nullptr;
965 while ((item = cont->getNextItem(item))) {
966 assert(item->def());
967 const objDef_t* obj = item->def();
968 if (item->isWeapon() && !item->mustReload() && ((obj->thrown && obj->oneshot && obj->deplete)
969 || Q_streq(obj->type, "grenade"))) {
970 weapon = item;
971 bestContainer = cont->def();
972 cost = bestContainer->out;
973 break;
974 }
975 }
976 }
977
978 *ip = weapon;
979 return bestContainer;
980}
981
987static bool AI_IsHandForForShootTypeFree (shoot_types_t shootType, Actor* actor)
988{
989 if (!IS_SHOT_REACTION(shootType)) {
990 if (IS_SHOT_RIGHT(shootType)) {
991 const Item* item = actor->getRightHandItem();
992 return item == nullptr;
993 }
994 if (IS_SHOT_LEFT(shootType)) {
995 const Item* left = actor->getLeftHandItem();
996 const Item* right = actor->getRightHandItem();
997 return left == nullptr && (right == nullptr || !right->isHeldTwoHanded());
998 }
999 }
1000
1001 return false;
1002}
1003
1010static int AI_CheckForMissionTargets (Actor* actor, const pos3_t pos)
1011{
1012 int bestActionScore = AI_ACTION_NOTHING_FOUND;
1013 int actionScore = 0;
1014 pos3_t oldPos;
1015
1016 VectorCopy(pos, oldPos);
1017 actor->setOrigin(pos);
1018 if (G_IsCivilian(actor)) {
1019 Edict* checkPoint = nullptr;
1020 int i = 0;
1021 /* find waypoints in a closer distance - if civilians are not close enough, let them walk
1022 * around until they came close */
1023 for (checkPoint = level.ai_waypointList; checkPoint != nullptr; checkPoint = checkPoint->groupChain) {
1024 if (!checkPoint->inuse)
1025 continue;
1026
1027 /* the lower the count value - the nearer the final target */
1028 if (checkPoint->count < actor->count) {
1029 if (VectorDist(actor->origin, checkPoint->origin) <= WAYPOINT_CIV_DIST) {
1030 const int actorTUs = actor->getUsableTUs();
1031 const int length = actorTUs - G_ActorMoveLength(actor, level.pathingMap, actor->pos, true);
1032 i++;
1033
1034 /* test for time and distance */
1035 actionScore = SCORE_MISSION_TARGET + length;
1036
1037 /* Don't walk to enemy ambush */
1038 Actor* check = nullptr;
1039 while ((check = G_EdictsGetNextLivingActorOfTeam(check, TEAM_ALIEN))) {
1040 const float dist = VectorDist(actor->origin, check->origin);
1041 /* @todo add visibility check here? */
1042 if (dist < RUN_AWAY_DIST)
1043 actionScore -= SCORE_RUN_AWAY;
1044 }
1045 if (actionScore > bestActionScore) {
1046 bestActionScore = actionScore;
1047 actor->count = checkPoint->count;
1048 }
1049 }
1050 }
1051 }
1052 /* reset the count value for this civilian to restart the search */
1053 if (!i)
1054 actor->count = 100;
1055 } else if (G_IsAlien(actor)) {
1056 /* search for a mission edict */
1057 Edict* mission = nullptr;
1058 while ((mission = G_EdictsGetNextInUse(mission))) {
1059 if (mission->type != ET_MISSION)
1060 continue;
1061 if (mission->pos[2] != actor->pos[2])
1062 continue;
1063 const int radius = mission->radius / (UNIT_SIZE + 1);
1064 const int distX = std::abs(actor->pos[0] - mission->pos[0]);
1065 const int distY = std::abs(actor->pos[1] - mission->pos[1]);
1066 const int dists = std::max(distX, distY);
1067 if (actor->getTeam() == mission->getTeam()) {
1068 actionScore = SCORE_MISSION_TARGET / (dists > radius ? dists - radius : 1);
1069 } else {
1070 /* try to prevent the phalanx from reaching their mission target */
1071 actionScore = SCORE_MISSION_OPPONENT_TARGET / (dists > radius ? dists - radius : 1);
1072 }
1073
1074 if (actionScore > bestActionScore) {
1075 bestActionScore = actionScore;
1076 }
1077 }
1078 }
1079
1080 actor->setOrigin(oldPos);
1081 return bestActionScore > AI_ACTION_NOTHING_FOUND ? bestActionScore : 0;
1082}
1083
1089static float AI_FighterCalcActionScore (Actor* actor, const pos3_t to, AiAction* aia)
1090{
1091 const pos_t move = G_ActorMoveLength(actor, level.pathingMap, to, true);
1092 if (move == ROUTING_NOT_REACHABLE)
1094
1095 int tu = actor->getUsableTUs() - move;
1096 /* test for time */
1097 if (tu < 0)
1099
1100 /* set basic parameters */
1101 aia->reset();
1102 VectorCopy(to, aia->to);
1103 VectorCopy(to, aia->stop);
1104 actor->setOrigin(aia->to);
1105
1106 /* pre-find a grenade */
1107 Item* grenade = nullptr;
1108 const invDef_t* fromCont = AI_SearchGrenade(actor, &grenade);
1109
1110 /* search best target */
1111 float maxDmg = 0.0f;
1112 float bestActionScore = 0.0f;
1113 int bestTime = -1;
1114 Actor* check = nullptr;
1115
1116 while ((check = G_EdictsGetNextLivingActor(check))) {
1117 if (check->isSamePosAs(aia->to) || !AI_IsHostile(actor, check))
1118 continue;
1119
1120 if (!G_IsVisibleForTeam(check, actor->getTeam()) && G_ActorVis(actor, check, true) < ACTOR_VIS_10)
1121 continue;
1122
1123 /* shooting */
1124 for (shoot_types_t shootType = ST_RIGHT; shootType < ST_NUM_SHOOT_TYPES; shootType++) {
1125 const bool freeHand = AI_IsHandForForShootTypeFree(shootType, actor);
1126 const Item* item = freeHand ? grenade : AI_GetItemForShootType(shootType, actor);
1127 if (!item)
1128 continue;
1129
1130 const fireDef_t* fdArray = item->getFiredefs();
1131 if (fdArray == nullptr)
1132 continue;
1133
1134 const invDef_t* toCont = INVDEF_FOR_SHOOTTYPE(shootType);
1135 const int invMoveCost = freeHand && grenade ? fromCont->out + toCont->in : 0;
1136 AI_FindBestFiredef(aia, actor, check, item, shootType, tu - invMoveCost, &maxDmg, &bestTime, fdArray);
1137 if (aia->shootType == shootType)
1138 bestTime += invMoveCost;
1139 }
1140 }
1141 /* add damage to bestActionScore */
1142 if (aia->target) {
1143 bestActionScore += maxDmg;
1144 assert(bestTime > 0);
1145 tu -= bestTime;
1146 }
1147
1148 /* Try not to stand in dangerous terrain (eg. fireField) */
1149 if (!AI_CheckPosition(actor, aia->to))
1150 bestActionScore -= SCORE_NOSAFE_POSITION_PENALTY;
1151
1152 if (!actor->isRaged()) {
1153 const int hidingTeam = AI_GetHidingTeam(actor);
1154 /* hide */
1155 if (!AI_HideNeeded(actor)) {
1156 /* is a hiding spot */
1157 bestActionScore += SCORE_HIDE + (aia->target ? SCORE_CLOSE_IN + SCORE_REACTION_FEAR_FACTOR : 0);
1158 } else if (aia->target && tu >= TU_MOVE_STRAIGHT) {
1159 /* reward short walking to shooting spot, when seen by enemies; */
1166 bestActionScore += std::max(SCORE_CLOSE_IN - move, 0);
1167
1168 if (!AI_FindHidingLocation(hidingTeam, actor, aia->to, tu)) {
1169 /* nothing found */
1170 actor->setOrigin(aia->to);
1171 } else {
1172 /* found a hiding spot */
1173 VectorCopy(actor->pos, aia->stop);
1174 actor->calcOrigin();
1175 bestActionScore += SCORE_HIDE;
1178 }
1179 }
1180 } else {
1181 if (aia->target)
1182 bestActionScore += aia->shots * SCORE_RAGE - move;
1183 else
1184 bestActionScore += move;
1185 }
1186
1187 if (aia->target) {
1188 const float dist = VectorDist(actor->origin, aia->target->origin);
1189 bestActionScore += SCORE_CLOSE_IN * (1.0f - dist / CLOSE_IN_DIST);
1190 } else if (actor->isRaged()) {
1191 /* reward closing in */
1192 float minDist = CLOSE_IN_DIST;
1193 check = nullptr;
1194 while ((check = G_EdictsGetNextLivingActor(check))) {
1195 if (!check->isSameTeamAs(actor)) {
1196 const float dist = VectorDist(actor->origin, check->origin);
1197 minDist = std::min(dist, minDist);
1198 }
1199 }
1200 bestActionScore += SCORE_CLOSE_IN * (1.0f - minDist / CLOSE_IN_DIST);
1201 } else {
1202 /* if no target available let them wander around until they find one */
1203 bestActionScore += SCORE_RANDOM * frand();
1204 }
1205
1206 /* Reward getting to mission objectives */
1207 if (!actor->isRaged())
1208 bestActionScore += AI_CheckForMissionTargets(actor, to);
1209
1210 /* penalize herding */
1211 if (!actor->isRaged()) {
1212 check = nullptr;
1213 while ((check = G_EdictsGetNextLivingActorOfTeam(check, actor->getTeam()))) {
1214 const float dist = VectorDist(actor->origin, check->origin);
1215 if (dist < HERD_THRESHOLD)
1216 bestActionScore -= SCORE_HERDING_PENALTY;
1217 }
1218 }
1219
1220 return bestActionScore;
1221}
1222
1231static float AI_CivilianCalcActionScore (Actor* actor, const pos3_t to, AiAction* aia)
1232{
1233 const pos_t move = G_ActorMoveLength(actor, level.pathingMap, to, true);
1234 const int tu = actor->getUsableTUs() - move;
1235
1236 /* test for time */
1237 if (tu < 0 || move == ROUTING_NOT_REACHABLE)
1239
1240 /* set basic parameters */
1241 aia->reset();
1242 VectorCopy(to, aia->to);
1243 VectorCopy(to, aia->stop);
1244 actor->setOrigin(to);
1245
1246 /* check whether this civilian can use weapons */
1247 if (actor->chr.teamDef) {
1248 const teamDef_t* teamDef = actor->chr.teamDef;
1249 if (!actor->isPanicked() && teamDef->weapons)
1250 return AI_FighterCalcActionScore(actor, to, aia);
1251 } else
1252 gi.DPrintf("AI_CivilianCalcActionScore: Error - civilian team with no teamdef\n");
1253
1254 /* run away */
1255 float minDist, minDistCivilian, minDistFighter;
1256 minDist = minDistCivilian = minDistFighter = RUN_AWAY_DIST * UNIT_SIZE;
1257
1258 Actor* check = nullptr;
1259 while ((check = G_EdictsGetNextLivingActor(check))) {
1260 float dist;
1261 if (actor == check)
1262 continue;
1263 dist = VectorDist(actor->origin, check->origin);
1264 /* if we are trying to walk to a position that is occupied by another actor already we just return */
1265 if (!dist)
1267 switch (check->getTeam()) {
1268 case TEAM_ALIEN:
1269 if (dist < minDist)
1270 minDist = dist;
1271 break;
1272 case TEAM_CIVILIAN:
1273 if (dist < minDistCivilian)
1274 minDistCivilian = dist;
1275 break;
1276 case TEAM_PHALANX:
1277 if (dist < minDistFighter)
1278 minDistFighter = dist;
1279 break;
1280 }
1281 }
1282
1283 minDist /= UNIT_SIZE;
1284 minDistCivilian /= UNIT_SIZE;
1285 minDistFighter /= UNIT_SIZE;
1286
1287 float delta;
1288 if (minDist < 8.0) {
1289 /* very near an alien: run away fast */
1290 delta = 4.0 * minDist;
1291 } else if (minDist < 16.0) {
1292 /* near an alien: run away */
1293 delta = 24.0 + minDist;
1294 } else if (minDist < 24.0) {
1295 /* near an alien: run away slower */
1296 delta = 40.0 + (minDist - 16) / 4;
1297 } else {
1298 delta = 42.0;
1299 }
1300 /* near a civilian: join him (1/3) */
1301 if (minDistCivilian < 10.0)
1302 delta += (10.0 - minDistCivilian) / 3.0;
1303 /* near a fighter: join him (1/5) */
1304 if (minDistFighter < 15.0)
1305 delta += (15.0 - minDistFighter) / 5.0;
1306 /* don't go close to a fighter to let him move */
1307 if (minDistFighter < 2.0)
1308 delta /= 10.0;
1309
1310 /* try to hide */
1311 float reactionTrap = 0.0;
1312 if (!actor->isInsane()) {
1313 check = nullptr;
1314 while ((check = G_EdictsGetNextLivingActor(check))) {
1315 if (actor == check)
1316 continue;
1317 if (!(G_IsAlien(check)))
1318 continue;
1319
1320 if (G_ActorVis(check, actor, true) > ACTOR_VIS_10)
1321 reactionTrap += SCORE_NONHIDING_PLACE_PENALTY;
1322 }
1323 }
1324 delta -= reactionTrap;
1325 float bestActionScore = delta;
1326
1327 /* Try not to stand in dangerous terrain */
1328 if (!AI_CheckPosition(actor, actor->pos))
1329 bestActionScore -= SCORE_NOSAFE_POSITION_PENALTY;
1330
1331 /* Approach waypoints */
1332 bestActionScore += AI_CheckForMissionTargets(actor, actor->pos);
1333
1334 /* add laziness */
1335 if (actor->getTus())
1336 bestActionScore += SCORE_CIV_LAZINESS * tu / actor->getTus();
1337 /* add random effects */
1338 bestActionScore += SCORE_CIV_RANDOM * frand();
1339
1340 return bestActionScore;
1341}
1342
1351static float AI_PanicCalcActionScore (Actor* actor, const pos3_t to, AiAction* aia)
1352{
1353 const pos_t move = G_ActorMoveLength(actor, level.pathingMap, to, true);
1354 const int tu = actor->getUsableTUs() - move;
1355
1356 /* test for time */
1357 if (tu < 0 || move == ROUTING_NOT_REACHABLE)
1359
1360 /* set basic parameters */
1361 aia->reset();
1362 VectorCopy(to, aia->to);
1363 VectorCopy(to, aia->stop);
1364 actor->setOrigin(to);
1365
1366 /* run away */
1367 float minDistFriendly, minDistOthers;
1368 minDistFriendly = minDistOthers = RUN_AWAY_DIST * UNIT_SIZE;
1369
1370 Actor* check = nullptr;
1371 while ((check = G_EdictsGetNextLivingActor(check))) {
1372 float dist;
1373 if (actor == check)
1374 continue;
1375 dist = VectorDist(actor->origin, check->origin);
1376 /* if we are trying to walk to a position that is occupied by another actor already we just return */
1377 if (!dist)
1379 if (check->isSameTeamAs(actor)) {
1380 if (dist < minDistFriendly)
1381 minDistFriendly = dist;
1382 } else {
1383 if (dist < minDistOthers)
1384 minDistOthers = dist;
1385 }
1386 }
1387
1388 minDistFriendly /= UNIT_SIZE;
1389 minDistOthers /= UNIT_SIZE;
1390
1391 float bestActionScore = SCORE_PANIC_RUN_TO_FRIENDS / minDistFriendly;
1392 bestActionScore -= SCORE_PANIC_FLEE_FROM_STRANGERS / minDistOthers;
1393
1394 /* try to hide */
1395 check = nullptr;
1396 if (!actor->isInsane())
1397 while ((check = G_EdictsGetNextLivingActor(check))) {
1398 if (actor == check)
1399 continue;
1400
1401 if (G_ActorVis(check, actor, true) > ACTOR_VIS_10)
1402 bestActionScore -= SCORE_NONHIDING_PLACE_PENALTY;
1403 }
1404
1405 /* Try not to stand in dangerous terrain */
1406 if (!AI_CheckPosition(actor, actor->pos))
1407 bestActionScore -= SCORE_NOSAFE_POSITION_PENALTY;
1408
1409 /* add random effects */
1410 bestActionScore += SCORE_PANIC_RANDOM * frand();
1411
1412 return bestActionScore;
1413}
1414
1423bool AI_FindMissionLocation (Actor* actor, const pos3_t to, int tus, int radius)
1424{
1425 int bestDist = ROUTING_NOT_REACHABLE;
1426 pos3_t bestPos = {to[0], to[1], to[2]};
1427
1428 AiAreaSearch searchArea(to, radius, true);
1429 while (searchArea.getNext(actor->pos)) {
1430 const pos_t length = G_ActorMoveLength(actor, level.pathingMap, actor->pos, true);
1431 /* Can't walk there */
1432 if (length == ROUTING_NOT_REACHABLE || length > tus)
1433 continue;
1434 /* Don't stand on dangerous terrain! */
1435 if (!AI_CheckPosition(actor, actor->pos))
1436 continue;
1437
1438 const int distX = std::abs(actor->pos[0] - to[0]);
1439 const int distY = std::abs(actor->pos[1] - to[1]);
1440 const int dist = std::max(distX, distY);
1441 if (dist < bestDist) {
1442 bestDist = dist;
1443 VectorCopy(actor->pos, bestPos);
1444 }
1445 }
1446 if (!VectorCompare(to, bestPos))
1447 VectorCopy(bestPos, actor->pos);
1448
1449 return bestDist < ROUTING_NOT_REACHABLE;
1450}
1451
1458static AiAction AI_PrepBestAction (const Player& player, Actor* actor)
1459{
1460 /* calculate move table */
1461 const int maxTU = actor->getUsableTUs();
1462 G_MoveCalc(0, actor, actor->pos, maxTU);
1463 Com_DPrintf(DEBUG_ENGINE, "AI_PrepBestAction: Called MoveMark.\n");
1464 gi.MoveStore(level.pathingMap);
1465
1466 /* set borders */
1467 const int dist = (maxTU + 1) / TU_MOVE_STRAIGHT;
1468
1469 /* search best action */
1470 pos3_t oldPos;
1471 vec3_t oldOrigin;
1472 VectorCopy(actor->pos, oldPos);
1473 VectorCopy(actor->origin, oldOrigin);
1474
1475 /* evaluate moving to every possible location in the search area,
1476 * including combat considerations */
1477 float bestActionScore, best = AI_ACTION_NOTHING_FOUND;
1478 AiAction aia, bestAia;
1479 pos3_t to;
1480 AiAreaSearch searchArea(oldPos, dist);
1481 while (searchArea.getNext(to)) {
1482 const pos_t move = G_ActorMoveLength(actor, level.pathingMap, to, true);
1483 if (move >= ROUTING_NOT_REACHABLE)
1484 continue;
1485 if (move > maxTU)
1486 continue;
1487
1488 if (G_IsCivilian(actor))
1489 bestActionScore = AI_CivilianCalcActionScore(actor, to, &aia);
1490 else if (actor->isPanicked())
1491 bestActionScore = AI_PanicCalcActionScore(actor, to, &aia);
1492 else
1493 bestActionScore = AI_FighterCalcActionScore(actor, to, &aia);
1494
1495 if (bestActionScore > best) {
1496 bestAia = aia;
1497 best = bestActionScore;
1498 }
1499 }
1500
1501 VectorCopy(oldPos, actor->pos);
1502 VectorCopy(oldOrigin, actor->origin);
1503
1504 /* nothing found to do */
1505 if (best == AI_ACTION_NOTHING_FOUND) {
1506 bestAia.target = nullptr;
1507 return bestAia;
1508 }
1509
1510 /* do the move */
1511 for (;;) {
1512 if (actor->isDead())
1513 break;
1514 G_ClientMove(player, 0, actor, bestAia.to);
1515 if (actor->isSamePosAs(bestAia.to))
1516 break;
1517 const pos_t length = G_ActorMoveLength(actor, level.pathingMap, bestAia.to, false);
1518 if (length > actor->getUsableTUs() || length >= ROUTING_NOT_REACHABLE)
1519 break;
1520 }
1521 /* test for possible death during move. reset bestAia due to dead status */
1522 if (actor->isDead())
1523 bestAia.reset();
1524
1525 /* if we are throwing a grenade from the inventory grab it now */
1526 if (bestAia.target && AI_IsHandForForShootTypeFree(bestAia.shootType, actor)) {
1527 Item* grenade = nullptr;
1528 const invDef_t* fromCont = AI_SearchGrenade(actor, &grenade);
1529 const invDef_t* toCont = INVDEF_FOR_SHOOTTYPE(bestAia.shootType);
1530 if (!grenade || !fromCont || !toCont || !G_ActorInvMove(actor, fromCont, grenade, toCont, NONE, NONE, true))
1531 bestAia.target = nullptr;
1532 }
1533
1534 return bestAia;
1535}
1536
1538{
1539 if (!level.ai_waypointList) {
1540 level.ai_waypointList = ent;
1541 return;
1542 }
1543
1544 Edict* e = level.ai_waypointList;
1545 while (e->groupChain) {
1546 e = e->groupChain;
1547 }
1548 e->groupChain = ent;
1549}
1550
1557void AI_TurnIntoDirection (Actor* actor, const pos3_t pos)
1558{
1559 const byte crouchingState = actor->isCrouched() ? 1 : 0;
1560 G_MoveCalc(actor->getTeam(), actor, pos, actor->getUsableTUs());
1561
1562 const int dvec = gi.MoveNext(level.pathingMap, pos, crouchingState);
1563 if (dvec != ROUTING_UNREACHABLE) {
1564 const byte dir = getDVdir(dvec);
1565 /* Only attempt to turn if the direction is not a vertical only action */
1566 if (dir < CORE_DIRECTIONS || dir >= FLYING_DIRECTIONS)
1567 G_ActorDoTurn(actor, dir & (CORE_DIRECTIONS - 1));
1568 }
1569}
1570
1575{
1576 if (G_ClientCanReload(actor, containerID)) {
1577 return G_ActorReload(actor, INVDEF(containerID));
1578 } else {
1579 G_ActorInvMove(actor, INVDEF(containerID), actor->getContainer(containerID), INVDEF(CID_FLOOR), NONE, NONE, true);
1580 G_ReactionFireSettingsUpdate(actor, actor->chr.RFmode.getFmIdx(), actor->chr.RFmode.getHand(), actor->chr.RFmode.getWeapon());
1581 }
1582 return false;
1583}
1584
1592static void AI_ActorThink (Player& player, Actor* actor)
1593{
1594 /* if a weapon can be reloaded we attempt to do so if TUs permit, otherwise drop it */
1595 Item* rightH = actor->getRightHandItem();
1596 Item* leftH = actor->getLeftHandItem();
1597 if (!actor->isPanicked()) {
1598 if (rightH && rightH->mustReload())
1600 if (leftH && leftH->mustReload())
1602 }
1603
1604 /* if both hands are empty, attempt to get a weapon out of backpack or the
1605 * floor (if TUs permit) */
1607 if (!actor->getLeftHandItem() && !actor->getRightHandItem())
1609
1610 AiAction bestAia = AI_PrepBestAction(player, actor);
1611
1612 /* shoot and hide */
1613 if (bestAia.target) {
1614 const fireDefIndex_t fdIdx = bestAia.fd ? bestAia.fd->fdIdx : 0;
1615 /* shoot until no shots are left or target died */
1616 while (bestAia.shots) {
1617 G_ClientShoot(player, actor, bestAia.target->pos, bestAia.shootType, fdIdx, nullptr, true, bestAia.z_align);
1618 bestAia.shots--;
1619 /* died by our own shot? */
1620 if (actor->isDead())
1621 return;
1622 /* check for target's death */
1623 if (G_IsDead(bestAia.target)) {
1624 /* search another target now */
1625 bestAia = AI_PrepBestAction(player, actor);
1626 /* no other target found - so no need to hide */
1627 if (!bestAia.target)
1628 return;
1629 }
1630 }
1631 actor->hiding = true;
1632
1633 /* now hide - for this we use the team of the alien actor because a phalanx soldier
1634 * might become visible during the hide movement */
1635 G_ClientMove(player, actor->getTeam(), actor, bestAia.stop);
1636 /* no shots left, but possible targets left - maybe they shoot back
1637 * or maybe they are still close after hiding */
1638
1639 /* decide whether the actor wants to crouch */
1640 if (AI_CheckCrouch(actor))
1641 G_ClientStateChange(player, actor, STATE_CROUCHED, true);
1642
1643 /* actor is still alive - try to turn into the appropriate direction to see the target
1644 * actor once he sees the ai, too */
1645 AI_TurnIntoDirection(actor, bestAia.target->pos);
1646
1649 /* G_ClientStateChange(player, actor->number, STATE_REACTION_ONCE, true); */
1650
1651 actor->hiding = false;
1652 }
1653}
1654
1655void AI_ActorRun (Player& player, Actor* actor)
1656{
1657 if (g_ailua->integer)
1658 AIL_ActorThink(player, actor);
1659 else
1660 AI_ActorThink(player, actor);
1661}
1662
1663#if 0
1664#include "g_ai2.cpp"
1665#else
1666static bool AI_TeamThink (Player& player)
1667{
1668 return false;
1669}
1670#endif
1671
1672static void AI_PlayerRun (Player& player)
1673{
1674 if (level.activeTeam != player.getTeam() || player.roundDone)
1675 return;
1676
1677 if (g_ailua->integer > 1) {
1678 if (AIL_TeamThink(player))
1679 /* did some thinking, come back next time */
1680 return;
1681 /* finished thinking, end round */
1682 }
1684 else if (player.getTeam() == TEAM_PHALANX && g_aihumans->integer == 2) {
1685 if (AI_TeamThink(player))
1686 return; /* did some thinking, come back next frame */
1687 /* finished thinking, end round */
1688 }
1689 else {
1690 /* find next actor to handle */
1691 Actor* actor = player.pers.getLastActor();
1692 while ((actor = G_EdictsGetNextLivingActorOfTeam(actor, player.getTeam()))) {
1693 const int beforeTUs = actor->getTus();
1694 if (beforeTUs > 0) {
1695 AI_ActorRun(player, actor);
1696 player.pers.setLastActor(actor);
1697
1698 if (beforeTUs > actor->getTus())
1699 return;
1700 }
1701 }
1702 }
1703
1704 /* nothing left to do, request endround */
1705 G_ClientEndRound(player);
1706 player.pers.setLastActor(nullptr);
1707}
1708
1713void AI_Run (void)
1714{
1715 /* don't run this too often to prevent overflows */
1716 if (level.framenum % 10)
1717 return;
1718
1719 /* set players to ai players and cycle over all of them */
1720 Player* player = nullptr;
1721 while ((player = G_PlayerGetNextActiveAI(player))) {
1722 AI_PlayerRun(*player);
1723 }
1724
1725 if (g_aihumans->integer) {
1726 player = nullptr;
1727 while ((player = G_PlayerGetNextActiveHuman(player))) {
1728 AI_PlayerRun(*player);
1729 }
1730 }
1731}
1732
1738static void AI_SetStats (Actor* actor, int team)
1739{
1740 const char* templateId = "";
1741 if (team != TEAM_CIVILIAN && gi.csi->numAlienTeams) {
1742 for (int i = 0; i < gi.csi->numAlienTeams; ++i) {
1743 if (gi.csi->alienTeams[i] == actor->chr.teamDef && gi.csi->alienChrTemplates[i]) {
1744 templateId = gi.csi->alienChrTemplates[i]->id;
1745 break;
1746 }
1747 }
1748 }
1749
1750 CHRSH_CharGenAbilitySkills(&actor->chr, G_IsMultiPlayer(), templateId);
1751
1752 actor->HP = actor->chr.HP;
1753 actor->setMorale(actor->chr.morale);
1754 actor->setStun(0);
1755
1756 /* hurt aliens in ufo crash missions (5%: almost dead, 10%: wounded, 15%: stunned) */
1757 if (level.hurtAliens && CHRSH_IsTeamDefAlien(actor->chr.teamDef)) {
1758 const float random = frand();
1759 int damage = 0, stun = 0;
1760 if (random <= 0.05f) {
1761 damage = actor->HP * 0.95f;
1762 } else if (random <= 0.15f) {
1763 stun = actor->HP * 0.3f;
1764 damage = actor->HP * 0.5f;
1765 } else if (random <= 0.3f) {
1766 stun = actor->HP * 0.75f;
1767 }
1768 actor->HP -= damage;
1769 if (!CHRSH_IsTeamDefRobot(actor->chr.teamDef))
1770 actor->setStun(stun);
1771
1772 for (int i = 0; i < actor->chr.teamDef->bodyTemplate->numBodyParts(); ++i)
1774 }
1775
1776 G_ActorGiveTimeUnits(actor);
1777}
1778
1784static void AI_SetCharacterValues (Edict* ent, int team)
1785{
1786 /* Set model. */
1787 const char* teamDefinition;
1788 if (team != TEAM_CIVILIAN) {
1789 if (gi.csi->numAlienTeams) {
1790 const int alienTeam = rand() % gi.csi->numAlienTeams;
1791 const teamDef_t* td = gi.csi->alienTeams[alienTeam];
1792 assert(td);
1793 teamDefinition = td->id;
1794 } else {
1795 teamDefinition = gi.Cvar_String("ai_alienteam");
1796 }
1797 } else {
1798 teamDefinition = gi.Cvar_String("ai_civilianteam");
1799 }
1800 gi.GetCharacterValues(teamDefinition, &ent->chr);
1801 if (!ent->chr.teamDef)
1802 gi.Error("Could not set teamDef for character: '%s'", teamDefinition);
1803}
1804
1805
1811static void AI_SetEquipment (Edict* ent, const equipDef_t* ed)
1812{
1813 /* Pack equipment. */
1814 game.invi.EquipActor(&ent->chr, ed, ent->chr.teamDef->onlyWeapon, ent->chr.score.skills[ABILITY_POWER]);
1815}
1816
1823static void AI_InitPlayer (const Player& player, Actor* actor, const equipDef_t* ed)
1824{
1825 const int team = player.getTeam();
1826
1827 /* Set the model and chose alien race. */
1828 AI_SetCharacterValues(actor, team);
1829
1830 /* Calculate stats. */
1831 AI_SetStats(actor, team);
1832
1833 /* Give equipment. */
1834 if (ed != nullptr)
1835 AI_SetEquipment(actor, ed);
1836
1837 /* after equipping the actor we can also get the model indices */
1838 actor->setBody(gi.ModelIndex(CHRSH_CharGetBody(&actor->chr)));
1839 actor->setHead(gi.ModelIndex(CHRSH_CharGetHead(&actor->chr)));
1840
1841 /* no need to call G_SendStats for the AI - reaction fire is serverside only for the AI */
1842 if (frand() < 0.75f) {
1843 G_ClientStateChange(player, actor, STATE_REACTION, false);
1844 }
1845
1846 /* initialize the LUA AI now */
1847 AIL_InitActor(actor);
1848}
1849
1850static const equipDef_t* G_GetEquipmentForAISpawn (int team)
1851{
1852 /* prepare equipment */
1853 if (team == TEAM_CIVILIAN)
1854 return nullptr;
1855
1856 const char* equipID = gi.Cvar_String("ai_equipment");
1857 const equipDef_t* ed = G_GetEquipDefByID(equipID);
1858 if (ed == nullptr)
1859 ed = &gi.csi->eds[0];
1860 return ed;
1861}
1862
1863static Actor* G_SpawnAIPlayer (const Player& player, const equipDef_t* ed)
1864{
1866 if (!actor) {
1867 gi.DPrintf("Not enough spawn points for team %i\n", player.getTeam());
1868 return nullptr;
1869 }
1870
1871 /* initialize the new actor */
1872 AI_InitPlayer(player, actor, ed);
1873
1874 G_TouchTriggers(actor);
1875
1876 gi.DPrintf("Spawned ai player for team %i with entnum %i (%s)\n", actor->getTeam(), actor->getIdNum(), actor->chr.name);
1877 G_CheckVis(actor, VT_PERISHCHK | VT_NEW);
1878 G_CheckVisTeamAll(actor->getTeam(), 0, actor);
1879
1880 return actor;
1881}
1882
1889static void G_SpawnAIPlayers (const Player& player, int numSpawn)
1890{
1891 const equipDef_t* ed = G_GetEquipmentForAISpawn(player.getTeam());
1892
1893 for (int i = 0; i < numSpawn; i++) {
1894 if (G_SpawnAIPlayer(player, ed) == nullptr)
1895 break;
1896 }
1897
1898 /* show visible actors */
1899 G_VisFlagsClear(player.getTeam());
1900 G_CheckVis(nullptr, 0);
1901}
1902
1908void AI_CheckRespawn (int team)
1909{
1910 if (!g_endlessaliens->integer)
1911 return;
1912
1913 if (team != TEAM_ALIEN)
1914 return;
1915
1916 const int spawned = level.initialAlienActorsSpawned;
1917 const int alive = level.num_alive[team];
1918 int diff = spawned - alive;
1919 const equipDef_t* ed = G_GetEquipmentForAISpawn(team);
1920
1921 while (diff > 0) {
1922 const Player* player = G_GetPlayerForTeam(team);
1923 Actor* actor = G_SpawnAIPlayer(*player, ed);
1924 if (actor == nullptr)
1925 break;
1926
1927 /* Some events need the actor added to the client before they are even *parsed*
1928 * - namely all the ones that rely on step times */
1929 G_EventActorAdd(PM_ALL, *actor, true);
1930 const playermask_t playerMask = G_VisToPM(actor->visflags);
1931 G_AppearPerishEvent(playerMask, true, *actor, nullptr);
1932
1933 diff--;
1934 }
1935}
1936
1944Player* AI_CreatePlayer (int team)
1945{
1946 if (!sv_ai->integer) {
1947 gi.DPrintf("AI deactivated - set sv_ai cvar to 1 to activate it\n");
1948 return nullptr;
1949 }
1950
1951 /* set players to ai players and cycle over all of them */
1952 Player* p = nullptr;
1953 while ((p = G_PlayerGetNextAI(p))) {
1954 if (p->isInUse())
1955 continue;
1956 p->reset();
1957 p->setInUse(true);
1958 p->setNum(p - game.players);
1959 p->pers.ai = true;
1960 G_SetTeamForPlayer(*p, team);
1961 if (p->getTeam() == TEAM_CIVILIAN) {
1962 G_SpawnAIPlayers(*p, ai_numcivilians->integer);
1963 } else {
1964 if (G_IsSinglePlayer())
1966 else
1968
1969 level.initialAlienActorsSpawned = level.num_spawned[p->getTeam()];
1970 }
1971
1972 gi.DPrintf("Created AI player (team %i)\n", p->getTeam());
1973 return p;
1974 }
1975
1976 /* nothing free */
1977 return nullptr;
1978}
void CHRSH_CharGenAbilitySkills(character_t *chr, bool multiplayer, const char *templateId)
Generates a skill and ability set for any character.
bool CHRSH_IsTeamDefRobot(const teamDef_t *const td)
Check if a team definition is a robot.
bool CHRSH_IsTeamDefAlien(const teamDef_t *const td)
Check if a team definition is alien.
const char * CHRSH_CharGetBody(const character_t *const chr)
Returns the body model for the soldiers for armoured and non armoured soldiers.
const char * CHRSH_CharGetHead(const character_t *const chr)
Returns the head model for the soldiers for armoured and non armoured soldiers.
@ ABILITY_POWER
Definition chr_shared.h:37
#define BODYPART_MAXTYPE
Definition chr_shared.h:266
#define INVDEF(containerID)
Definition cl_shared.h:48
An Edict of type Actor.
Definition g_edict.h:348
bool isDead() const
Definition g_edict.h:362
void setBody(unsigned int body_)
Definition g_edict.h:387
bool isPanicked() const
Definition g_edict.h:356
bool isInsane() const
Definition g_edict.h:359
int getUsableTUs() const
Calculates the amount of usable TUs. This is without the reserved TUs.
Definition g_edict.h:400
bool isStunned() const
Definition g_edict.h:355
bool isRaged() const
Definition g_edict.h:358
bool isReaction() const
Definition g_edict.h:357
bool isCrouched() const
Definition g_edict.h:361
void setHead(unsigned int head_)
Definition g_edict.h:393
int z_align
Definition g_ai.cpp:46
byte shots
Definition g_ai.cpp:43
void reset()
Definition g_ai.cpp:48
pos3_t stop
Definition g_ai.cpp:41
Edict * target
Definition g_ai.cpp:44
const fireDef_t * fd
Definition g_ai.cpp:45
pos3_t to
Definition g_ai.cpp:40
shoot_types_t shootType
Definition g_ai.cpp:42
bool isEmpty(void) const
Checks if the queue is empty.
Definition g_ai.h:59
qnode_s * _tail
Definition g_ai.h:67
void clear(void)
Remove all data from the queue.
Definition g_ai.cpp:127
void enqueue(const pos3_t data)
Add an entry to the queue.
Definition g_ai.cpp:92
qnode_s * _head
Definition g_ai.h:66
~LQueue(void)
Clear LQueue internal data.
Definition g_ai.cpp:84
bool dequeue(pos3_t data)
Retrieve an entry form the queue.
Definition g_ai.cpp:111
AiAreaSearch class, used to get an area of the map around a certain position for the AI to check poss...
Definition g_ai.h:33
LQueue _area
Definition g_ai.h:70
void plotArea(const pos3_t origin, int radius, bool flat=false)
Calculate the search area.
Definition g_ai.cpp:143
void plotCircle(const pos3_t origin, int radius)
Definition g_ai.cpp:165
void plotPos(const pos3_t origin, int xOfs, int yOfs)
Definition g_ai.cpp:203
AiAreaSearch()
Initializes an AiAreaSearch object to default values.
Definition g_ai.cpp:56
~AiAreaSearch(void)
Clear AiAreaSearch internal data.
Definition g_ai.cpp:70
bool getNext(pos3_t pos)
Get next position in the search area.
Definition g_ai.cpp:79
short numBodyParts(void) const
short getRandomBodyPart(void) const
Item * getNextItem(const Item *prev) const
const invDef_t * def() const
teammask_t visflags
Definition g_edict.h:82
bool isSameTeamAs(const Edict *other) const
Definition g_edict.h:278
bool hiding
Definition g_edict.h:142
character_t chr
Definition g_edict.h:116
int getIdNum() const
Definition g_edict.h:231
byte dmgtype
Definition g_edict.h:139
bool isSameAs(const Edict *other) const
Definition g_edict.h:275
void setMorale(int mor)
Definition g_edict.h:311
Item * getRightHandItem() const
Definition g_edict.h:249
vec3_t origin
Definition g_edict.h:53
pos3_t pos
Definition g_edict.h:55
int HP
Definition g_edict.h:89
int doorState
Definition g_edict.h:158
Edict * groupChain
Definition g_edict.h:167
void setOrigin(const pos3_t newPos)
Set the edict's pos and origin vector to the given grid position.
Definition g_edict.h:223
Item * getLeftHandItem() const
Definition g_edict.h:252
bool inuse
Definition g_edict.h:47
int getTeam() const
Definition g_edict.h:269
void calcOrigin()
Calculate the edict's origin vector from it's grid position.
Definition g_edict.h:216
Player & getPlayer() const
Definition g_edict.h:265
bool isOpponent(const Actor *actor) const
Check if given actor is an enemy.
Definition g_edicts.cpp:383
int dmg
Definition g_edict.h:138
int count
Definition g_edict.h:135
bool isSamePosAs(const pos3_t cmpPos)
Check whether the edict is on the given position.
Definition g_edict.h:286
int radius
Definition g_edict.h:123
entity_type_t type
Definition g_edict.h:81
Item * getContainer(const containerIndex_t idx) const
Definition g_edict.h:243
void setStun(int stu)
Definition g_edict.h:302
int getTus() const
Definition g_edict.h:318
int morale
Definition g_edict.h:91
actorHands_t getHand() const
Definition chr_shared.h:165
const objDef_t * getWeapon() const
Definition chr_shared.h:161
int getFmIdx() const
Definition chr_shared.h:157
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
item instance data, with linked list capability
Definition inv_shared.h:402
const objDef_t * ammoDef(void) const
Definition inv_shared.h:460
bool mustReload() const
Definition inv_shared.h:483
const objDef_t * def(void) const
Definition inv_shared.h:469
bool isWeapon() const
Definition inv_shared.h:486
const fireDef_t * getFiredefs() const
Returns the firedefinitions for a given weapon/ammo.
bool isHeldTwoHanded() const
Definition inv_shared.h:476
Definition line.h:31
void Com_DPrintf(int level, const char *fmt,...)
A Com_Printf that only shows up if the "developer" cvar is set.
Definition common.cpp:440
#define MASK_SHOT
Definition defines.h:275
#define ROUTING_NOT_REACHABLE
Definition defines.h:283
#define TU_MOVE_STRAIGHT
Definition defines.h:74
#define NONE
Definition defines.h:68
#define STATE_OPENED
Definition defines.h:87
#define PATHFINDING_WIDTH
absolute max
Definition defines.h:292
#define DEBUG_ENGINE
Definition defines.h:56
#define UNIT_SIZE
Definition defines.h:121
#define ROUTING_UNREACHABLE
Definition defines.h:284
#define ACTOR_SIZE_NORMAL
Definition defines.h:302
#define PATHFINDING_HEIGHT
15 max, adjusting above 8 will require a rewrite to the DV code
Definition defines.h:294
#define GROUND_DELTA
Definition defines.h:115
bool G_ActorReload(Actor *actor, const invDef_t *invDef)
Reload weapon with actor.
Definition g_actor.cpp:714
void G_ActorGiveTimeUnits(Actor *actor)
Set time units for the given edict. Based on speed skills.
Definition g_actor.cpp:260
int G_ActorGetModifiedTimeForFiredef(const Edict *const ent, const fireDef_t *const fd, const bool reaction)
Definition g_actor.cpp:764
int G_ActorDoTurn(Edict *ent, byte dir)
Turns an actor around.
Definition g_actor.cpp:154
bool G_ActorInvMove(Actor *actor, const invDef_t *fromContType, Item *fItem, const invDef_t *toContType, int tx, int ty, bool checkaction)
Moves an item inside an inventory. Floors are handled special.
Definition g_actor.cpp:506
#define G_IsCrouched(ent)
Definition g_actor.h:32
#define G_IsDead(ent)
Definition g_actor.h:34
#define SCORE_NOSAFE_POSITION_PENALTY
Definition g_ai.cpp:239
static void AI_SetEquipment(Edict *ent, const equipDef_t *ed)
Sets the actor's equipment.
Definition g_ai.cpp:1811
static void AI_ActorThink(Player &player, Actor *actor)
The think function for the ai controlled aliens or panicked humans.
Definition g_ai.cpp:1592
#define SCORE_MISSION_OPPONENT_TARGET
Definition g_ai.cpp:241
#define SCORE_KILL
Definition g_ai.cpp:220
static bool AI_CheckFF(const Edict *ent, const vec3_t target, float spread, float radius)
Check whether friendly units are in the line of fire when shooting.
Definition g_ai.cpp:322
#define SCORE_DAMAGE_WORTH_FACTOR
Definition g_ai.cpp:230
static void AI_SetCharacterValues(Edict *ent, int team)
Sets an actor's character values.
Definition g_ai.cpp:1784
static pathing_t * hidePathingTable
Definition g_ai.cpp:263
static Actor * G_SpawnAIPlayer(const Player &player, const equipDef_t *ed)
Definition g_ai.cpp:1863
static Edict * AI_SearchDestroyableObject(const Actor *actor, const fireDef_t *fd)
Definition g_ai.cpp:737
#define CLOSE_IN_DIST
Definition g_ai.cpp:252
static const Item * AI_GetItemFromInventory(const Item *ic)
Returns useable item from the given inventory list. That means that the 'weapon' has ammunition left ...
Definition g_ai.cpp:522
#define SCORE_RANDOM
Definition g_ai.cpp:221
#define WAYPOINT_CIV_DIST
Definition g_ai.cpp:236
static void AI_InitPlayer(const Player &player, Actor *actor, const equipDef_t *ed)
Initializes the actor.
Definition g_ai.cpp:1823
#define CALC_DAMAGE_SAMPLES
Definition g_ai.cpp:260
static const equipDef_t * G_GetEquipmentForAISpawn(int team)
Definition g_ai.cpp:1850
#define SCORE_RUN_AWAY
Definition g_ai.cpp:233
#define SCORE_CIV_LAZINESS
Definition g_ai.cpp:234
#define HIDE_DIST
distance for (ai) hiding in grid tiles
Definition g_ai.cpp:256
static bool AI_IsExposed(int team, Actor *check)
Test if check is exposed to the enemy team.
Definition g_ai.cpp:298
static AiAction AI_PrepBestAction(const Player &player, Actor *actor)
Attempts to find the best action for an alien. Moves the alien into the starting position for that ac...
Definition g_ai.cpp:1458
#define SCORE_CIV_FACTOR
Definition g_ai.cpp:228
#define INVDEF_FOR_SHOOTTYPE(st)
Definition g_ai.cpp:261
static float AI_PanicCalcActionScore(Actor *actor, const pos3_t to, AiAction *aia)
Calculates possible actions for a panicking unit.
Definition g_ai.cpp:1351
static void AI_SetStats(Actor *actor, int team)
Initializes the actor's stats like morals, strength and so on.
Definition g_ai.cpp:1738
static bool AI_IsHandForForShootTypeFree(shoot_types_t shootType, Actor *actor)
Check if the hand for the given shoot type is free.
Definition g_ai.cpp:987
#define RUN_AWAY_DIST
Definition g_ai.cpp:235
static float AI_FighterCalcActionScore(Actor *actor, const pos3_t to, AiAction *aia)
Definition g_ai.cpp:1089
#define HERD_THRESHOLD
Definition g_ai.cpp:237
static pathing_t * herdPathingTable
Definition g_ai.cpp:264
#define SCORE_PANIC_RUN_TO_FRIENDS
Definition g_ai.cpp:246
static void AI_FindBestFiredef(AiAction *aia, Actor *actor, Actor *check, const Item *item, shoot_types_t shootType, int tu, float *maxDmg, int *bestTime, const fireDef_t *fdArray)
Definition g_ai.cpp:831
#define AI_ACTION_NOTHING_FOUND
Definition g_ai.cpp:250
#define SCORE_HIDE
Definition g_ai.cpp:218
#define SCORE_REACTION_ERADICATION
Definition g_ai.cpp:222
static float AI_CivilianCalcActionScore(Actor *actor, const pos3_t to, AiAction *aia)
Calculates possible actions for a civilian.
Definition g_ai.cpp:1231
static bool AI_CheckCrouch(const Actor *actor)
Checks whether it would be smart to change the state to STATE_CROUCHED.
Definition g_ai.cpp:452
#define LOF_CHECK_PARTITIONS
Definition g_ai.cpp:762
bool AI_IsHostile(const Actor *actor, const Edict *target)
Check if actor perceives target as hostile.
Definition g_ai.cpp:933
#define SCORE_RAGE
Definition g_ai.cpp:225
#define SCORE_NONHIDING_PLACE_PENALTY
Definition g_ai.cpp:224
#define SCORE_HERDING_PENALTY
Definition g_ai.cpp:238
#define SCORE_PANIC_RANDOM
Definition g_ai.cpp:248
#define SCORE_DAMAGE
Definition g_ai.cpp:226
#define HERD_DIST
Definition g_ai.cpp:257
#define SCORE_REACTION_FEAR_FACTOR
Definition g_ai.cpp:223
#define SCORE_CIV_RANDOM
Definition g_ai.cpp:232
static bool AI_TeamThink(Player &player)
Definition g_ai.cpp:1666
#define SCORE_CLOSE_IN
Definition g_ai.cpp:219
static void AI_PlayerRun(Player &player)
Definition g_ai.cpp:1672
#define SCORE_DISABLED_FACTOR
Definition g_ai.cpp:229
#define SCORE_PANIC_FLEE_FROM_STRANGERS
Definition g_ai.cpp:247
static void G_SpawnAIPlayers(const Player &player, int numSpawn)
Spawn civilians and aliens.
Definition g_ai.cpp:1889
static int AI_CheckForMissionTargets(Actor *actor, const pos3_t pos)
Searches the map for mission edicts and try to get there.
Definition g_ai.cpp:1010
#define SCORE_MISSION_TARGET
Definition g_ai.cpp:242
#define SCORE_DAMAGE_FACTOR
Definition g_ai.cpp:227
Artificial Intelligence functions.
bool AI_CheckLineOfFire(const Actor *shooter, const Edict *target, const fireDef_t *fd, int shots)
Definition g_ai.cpp:763
bool AI_TryToReloadWeapon(Actor *actor, containerIndex_t containerID)
if a weapon can be reloaded we attempt to do so if TUs permit, otherwise drop it
Definition g_ai.cpp:1574
bool AI_CheckUsingDoor(const Edict *ent, const Edict *door)
Checks whether the AI controlled actor wants to use a door.
Definition g_ai.cpp:396
void AI_Init(void)
Definition g_ai.cpp:266
int AIL_InitActor(Actor *actor)
Initializes the lua AI for an actor.
void AI_ActorRun(Player &player, Actor *actor)
Definition g_ai.cpp:1655
void AI_TurnIntoDirection(Actor *actor, const pos3_t pos)
This function will turn the AI actor into the direction that is needed to walk to the given location.
Definition g_ai.cpp:1557
bool AI_HideNeeded(const Actor *actor)
Checks whether the given alien should try to hide because there are enemies close enough to shoot the...
Definition g_ai.cpp:484
void G_AddToWayPointList(Edict *ent)
Definition g_ai.cpp:1537
bool AI_FindHerdLocation(Actor *actor, const pos3_t from, const vec3_t target, int tu, bool inverse)
Tries to search a spot where actor will be more closer to the target and behind the target from enemy...
Definition g_ai.cpp:659
const Item * AI_GetItemForShootType(shoot_types_t shootType, const Edict *ent)
Definition g_ai.cpp:541
int AI_GetHidingTeam(const Edict *ent)
Returns the value for the vis check whenever an ai actor tries to hide. For aliens this is the invers...
Definition g_ai.cpp:570
bool AI_FindMissionLocation(Actor *actor, const pos3_t to, int tus, int radius=0)
Try to go close to a mission edict.
Definition g_ai.cpp:1423
float AI_CalcShotDamage(Actor *actor, const Actor *target, const fireDef_t *fd, shoot_types_t shotType)
Calculate estimated damage per single shoot.
Definition g_ai.cpp:811
bool AI_FighterCheckShoot(const Actor *actor, const Edict *check, const fireDef_t *fd, float dist)
Check whether the fighter should perform the shoot.
Definition g_ai.cpp:364
bool AI_FindHidingLocation(int team, Actor *actor, const pos3_t from, int tuLeft)
Tries to search a hiding spot.
Definition g_ai.cpp:606
bool AIL_TeamThink(Player &player)
The team think function for the ai controlled players.
bool AI_IsHostile(const Actor *actor, const Edict *target)
Check if actor perceives target as hostile.
Definition g_ai.cpp:933
bool AI_CheckPosition(const Actor *const ent, const pos3_t pos)
Checks if the given position is safe to stand on.
Definition g_ai.cpp:581
void AI_Run(void)
Every server frame one single actor is handled - always in the same order.
Definition g_ai.cpp:1713
const invDef_t * AI_SearchGrenade(const Actor *actor, Item **ip)
Search the edict's inventory for a grenade or other one-use weapon.
Definition g_ai.cpp:954
bool AI_HasLineOfFire(const Actor *actor, const Edict *target)
Check if actor has a line of fire to the target given.
Definition g_ai.cpp:275
void AIL_ActorThink(Player &player, Actor *actor)
The think function for the ai controlled players.
void AI_CheckRespawn(int team)
If the cvar g_endlessaliens is set we will endlessly respawn aliens.
Definition g_ai.cpp:1908
Player * AI_CreatePlayer(int team)
Spawn civilians and aliens.
Definition g_ai.cpp:1944
bool G_SetTeamForPlayer(Player &player, const int team)
Set the used team for the given player.
Definition g_client.cpp:852
Player * G_PlayerGetNextActiveHuman(Player *lastPlayer)
Iterate through the list of players.
Definition g_client.cpp:110
Player * G_PlayerGetNextActiveAI(Player *lastPlayer)
Iterate through the list of players.
Definition g_client.cpp:126
void G_ClientStateChange(const Player &player, Actor *actor, int reqState, bool checkaction)
Changes the state of a player/soldier.
Definition g_client.cpp:473
void G_AppearPerishEvent(playermask_t playerMask, bool appear, Edict &check, const Edict *ent)
Send the appear or perish event to the affected clients.
Definition g_client.cpp:245
Player * G_PlayerGetNextAI(Player *lastPlayer)
Iterate through the list of players.
Definition g_client.cpp:84
Actor * G_ClientGetFreeSpawnPointForActorSize(const Player &player, const actorSizeEnum_t actorSize)
Searches a free spawning point for a given actor size and turns it into an actor.
bool G_ClientCanReload(Actor *actor, containerIndex_t containerID)
Returns true if actor can reload weapon.
Definition g_client.cpp:536
bool G_ClientGetWeaponFromInventory(Actor *actor)
Retrieve or collect a loaded weapon from any linked container for the actor's right hand.
Definition g_client.cpp:568
playermask_t G_VisToPM(teammask_t teamMask)
Converts vis mask to player mask.
Definition g_client.cpp:186
Interface for g_client.cpp.
void G_CalcEffectiveSpread(const Actor *shooter, const fireDef_t *fd, vec2_t effSpread)
Calculate the effective spread for the given actor and firemode.
Definition g_combat.cpp:671
int G_ApplyProtection(const Edict *target, const byte dmgWeight, int damage)
Reduces damage by armour and natural protection.
Definition g_combat.cpp:362
bool G_ClientShoot(const Player &player, Actor *actor, const pos3_t at, shoot_types_t shootType, fireDefIndex_t firemode, shot_mock_t *mock, bool allowReaction, int z_align)
Setup for shooting, either real or mock.
All parts of the main game logic that are combat related.
Actor * G_EdictsGetNextLivingActorOfTeam(Actor *lastEnt, const int team)
Iterate through the living actor entities of the given team.
Definition g_edicts.cpp:216
Actor * G_EdictsGetNextLivingActor(Actor *lastEnt)
Iterate through the living actor entities.
Definition g_edicts.cpp:196
Edict * G_EdictsGetNextInUse(Edict *lastEnt)
Iterate through the entities that are in use.
Definition g_edicts.cpp:166
Edict * G_EdictsGetByNum(const int num)
Get an entity by it's number.
Definition g_edicts.cpp:83
functions to handle the storage and lifecycle of all edicts in the game module.
void G_EventActorAdd(playermask_t playerMask, const Edict &ent, const bool instant)
Definition g_events.cpp:511
#define PM_ALL
Definition g_events.h:36
unsigned int playermask_t
Definition g_events.h:34
const equipDef_t * G_GetEquipDefByID(const char *equipID)
#define TAG_LEVEL
Definition g_local.h:59
cvar_t * ai_singleplayeraliens
Definition g_main.cpp:72
game_locals_t game
Definition g_main.cpp:37
level_locals_t level
Definition g_main.cpp:38
cvar_t * ai_numcivilians
Definition g_main.cpp:73
#define G_MemFree(ptr)
Definition g_local.h:65
#define G_IsBreakable(ent)
Definition g_local.h:137
#define G_IsVisibleForTeam(ent, team)
Definition g_local.h:144
game_import_t gi
Definition g_main.cpp:39
#define G_TagMalloc(size, tag)
Definition g_local.h:64
cvar_t * g_aihumans
Definition g_main.cpp:113
cvar_t * mor_brave
Definition g_main.cpp:104
#define G_IsCivilian(ent)
Definition g_local.h:148
#define G_IsMultiPlayer()
Definition g_local.h:145
void G_ClientEndRound(Player &player)
Definition g_round.cpp:184
#define G_IsSinglePlayer()
Definition g_local.h:146
cvar_t * g_endlessaliens
Definition g_main.cpp:111
#define G_IsAlien(ent)
Definition g_local.h:149
cvar_t * ai_multiplayeraliens
Definition g_main.cpp:74
#define G_IsBrushModel(ent)
Definition g_local.h:138
cvar_t * sv_ai
Definition g_main.cpp:60
cvar_t * g_ailua
Definition g_main.cpp:112
pos_t G_ActorMoveLength(const Actor *actor, const pathing_t *path, const pos3_t to, bool stored)
Return the needed TUs to walk to a given position.
Definition g_move.cpp:270
void G_ClientMove(const Player &player, int visTeam, Actor *actor, const pos3_t to)
Generates the client events that are send over the netchannel to move an actor.
Definition g_move.cpp:307
void G_MoveCalcLocal(pathing_t *pt, int team, const Edict *movingActor, const pos3_t from, int distance)
Same as G_MoveCalc, except that it uses the pathing table passed as the first param.
Definition g_move.cpp:101
void G_MoveCalc(int team, const Actor *movingActor, const pos3_t from, int distance)
Precalculates a move table for a given team and a given starting position. This will calculate a rout...
Definition g_move.cpp:88
void G_ReactionFireSettingsUpdate(Actor *actor, fireDefIndex_t fmIdx, actorHands_t hand, const objDef_t *od)
Updates the reaction fire settings in case something was moved into a hand or from a hand that would ...
Reaction fire system.
trace_t G_Trace(const Line &trLine, const Edict *passent, int contentmask)
collision detection - this version is more accurate and includes entity tests
Definition g_utils.cpp:265
int G_TouchTriggers(Edict *ent, const entity_type_t type)
Check the world against triggers for the current entity.
Definition g_utils.cpp:547
Player * G_GetPlayerForTeam(int team)
Gets player for given team.
Definition g_utils.cpp:188
Misc utility functions for game module.
int G_TestVis(const int team, Edict *check, const vischeckflags_t flags)
test if check is visible by team (or if visibility changed?)
Definition g_vis.cpp:255
bool G_FrustumVis(const Edict *from, const vec3_t point)
Checks whether a point is "visible" from the edicts position.
Definition g_vis.cpp:38
int G_VisCheckDist(const Edict *const ent)
Definition g_vis.cpp:163
void G_VisFlagsClear(int team)
Reset the visflags for all edicts in the global list for the given team - and only for the given team...
Definition g_vis.cpp:424
int G_CheckVisTeamAll(const int team, const vischeckflags_t visFlags, const Edict *ent)
Do G_CheckVisTeam for all entities ent is the one that is looking at the others.
Definition g_vis.cpp:376
bool G_Vis(const int team, const Edict *from, const Edict *check, const vischeckflags_t flags)
test if check is visible by from
Definition g_vis.cpp:183
void G_CheckVis(Edict *check, const vischeckflags_t visFlags)
Check if the edict appears/perishes for the other teams. If they appear for other teams,...
Definition g_vis.cpp:409
float G_ActorVis(const Edict *ent, const Edict *check, bool full)
calculate how much check is "visible" by ent
Definition g_vis.cpp:100
#define ACTOR_VIS_0
Definition g_vis.h:64
#define ACTOR_VIS_10
Definition g_vis.h:63
#define VT_NOFRUSTUM
Definition g_vis.h:55
#define VT_PERISHCHK
Definition g_vis.h:53
#define VS_YES
Definition g_vis.h:46
#define VT_NEW
Definition g_vis.h:58
#define ACTOR_VIS_50
Definition g_vis.h:62
int32_t fireDefIndex_t
Definition inv_shared.h:78
int32_t containerIndex_t
Definition inv_shared.h:46
#define CID_FLOOR
Definition inv_shared.h:55
#define CID_LEFT
Definition inv_shared.h:48
#define CID_RIGHT
Definition inv_shared.h:47
voidpf uLong int origin
Definition ioapi.h:45
float crand(void)
Return random values between -1 and 1.
Definition mathlib.cpp:517
const pos3_t pos3_origin
Definition mathlib.cpp:37
float frand(void)
Return random values between 0 and 1.
Definition mathlib.cpp:506
void VectorNormalizeFast(vec3_t v)
fast vector normalize routine that does not check to make sure that length != 0, nor does it return l...
Definition mathlib.cpp:762
#define PosToVec(p, v)
Pos boundary size is +/- 128 - to get into the positive area we add the possible max negative value a...
Definition mathlib.h:110
#define FLYING_DIRECTIONS
Definition mathlib.h:89
#define CORE_DIRECTIONS
Definition mathlib.h:88
#define torad
Definition mathlib.h:50
#define getDVdir(dv)
Definition mathlib.h:249
#define TEAM_PHALANX
Definition q_shared.h:62
#define IS_SHOT_REACTION(x)
Determine whether the selected shoot type is for reaction fire.
Definition q_shared.h:239
#define TEAM_ALIEN
Definition q_shared.h:63
#define IS_SHOT_RIGHT(x)
Determine whether the selected shoot type is for the item in the right hand, either shooting or react...
Definition q_shared.h:243
#define IS_SHOT_HEADGEAR(x)
Determine whether the selected shoot type is for the item in the headgear slot.
Definition q_shared.h:245
#define GRAVITY
Definition q_shared.h:276
@ ET_MISSION
Definition q_shared.h:162
#define STATE_CROUCHED
Definition q_shared.h:263
#define STATE_REACTION
Definition q_shared.h:272
#define ST_NUM_SHOOT_TYPES
Amount of shoottypes available.
Definition q_shared.h:236
int32_t shoot_types_t
Available shoot types - also see the ST_ constants.
Definition q_shared.h:206
#define ST_RIGHT
The right hand should be used for shooting.
Definition q_shared.h:211
#define TEAM_CIVILIAN
Definition q_shared.h:61
#define IS_SHOT_LEFT(x)
Determine whether the selected shoot type is for the item in the left hand, either shooting or reacti...
Definition q_shared.h:241
#define PLAYER_WIDTH
Definition q_sizes.h:10
QGL_EXTERN int GLboolean GLfloat * v
Definition r_gl.h:120
QGL_EXTERN GLsizei const GLvoid * data
Definition r_gl.h:89
QGL_EXTERN GLuint GLsizei GLsizei * length
Definition r_gl.h:110
QGL_EXTERN GLint i
Definition r_gl.h:113
#define Q_streq(a, b)
Definition shared.h:136
#define OBJZERO(obj)
Definition shared.h:178
const teamDef_t * teamDef
Definition chr_shared.h:413
chrScoreGlobal_t score
Definition chr_shared.h:406
FiremodeSettings RFmode
Definition chr_shared.h:416
woundInfo_t wounds
Definition chr_shared.h:402
char name[MAX_VAR]
Definition chr_shared.h:390
Inventory inv
Definition chr_shared.h:411
int skills[SKILL_NUM_TYPES]
Definition chr_shared.h:122
this is a fire definition for our weapons/ammo
Definition inv_shared.h:110
fireDefIndex_t fdIdx
Definition inv_shared.h:130
vec2_t damage
Definition inv_shared.h:158
float splrad
Definition inv_shared.h:161
vec2_t spldmg
Definition inv_shared.h:160
weaponFireDefIndex_t weapFdsIdx
Definition inv_shared.h:126
void getShotOrigin(const vec3_t from, const vec3_t dir, bool crouching, vec3_t shotOrigin) const
float range
Definition inv_shared.h:152
bool rolled
Definition inv_shared.h:138
bool launched
Definition inv_shared.h:137
bool gravity
Definition inv_shared.h:136
inventory definition for our menus
Definition inv_shared.h:371
Defines all attributes of objects used in the inventory.
Definition inv_shared.h:264
const char * type
Definition inv_shared.h:271
bool deplete
Definition inv_shared.h:301
bool oneshot
Definition inv_shared.h:299
byte dmgtype
Definition inv_shared.h:325
bool thrown
Definition inv_shared.h:281
used in shot probability calculations (pseudo shots)
Definition g_combat.h:31
int friendCount
Definition g_combat.h:33
int civilian
Definition g_combat.h:34
int enemyCount
Definition g_combat.h:32
bool weapons
Definition chr_shared.h:335
const objDef_t * onlyWeapon
Definition chr_shared.h:336
char id[MAX_VAR]
Definition chr_shared.h:309
const BodyData * bodyTemplate
Definition chr_shared.h:350
float fraction
Definition tracing.h:58
int entNum
Definition tracing.h:67
int treatmentLevel[BODYPART_MAXTYPE]
Definition chr_shared.h:363
pos_t pos3_t[3]
Definition ufotypes.h:58
byte pos_t
Definition ufotypes.h:57
float vec_t
Definition ufotypes.h:37
vec_t vec3_t[3]
Definition ufotypes.h:39
vec_t vec2_t[2]
Definition ufotypes.h:38
static int oldPos
#define VectorDist(a, b)
Definition vector.h:69
#define VectorSubtract(a, b, dest)
Definition vector.h:45
#define VectorCopy(src, dest)
Definition vector.h:51
#define VectorCompare(a, b)
Definition vector.h:63
#define VectorDistSqr(a, b)
Definition vector.h:68
#define VectorAdd(a, b, dest)
Definition vector.h:47
#define DotProduct(x, y)
Returns the distance between two 3-dimensional vectors.
Definition vector.h:44
#define VectorScale(in, scale, out)
Definition vector.h:79