g_ai.c

Go to the documentation of this file.
00001 
00006 /*
00007 Copyright (C) 2002-2010 UFO: Alien Invasion.
00008 
00009 This program is free software; you can redistribute it and/or
00010 modify it under the terms of the GNU General Public License
00011 as published by the Free Software Foundation; either version 2
00012 of the License, or (at your option) any later version.
00013 
00014 This program is distributed in the hope that it will be useful,
00015 but WITHOUT ANY WARRANTY; without even the implied warranty of
00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00017 
00018 See the GNU General Public License for more details.
00019 
00020 You should have received a copy of the GNU General Public License
00021 along with this program; if not, write to the Free Software
00022 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00023 
00024 */
00025 
00026 #include "g_local.h"
00027 #include "g_ai.h"
00028 
00035 static qboolean AI_CheckFF (const edict_t * ent, const vec3_t target, float spread)
00036 {
00037     edict_t *check = NULL;
00038     vec3_t dtarget, dcheck, back;
00039     float cosSpread;
00040 
00041     /* spread data */
00042     if (spread < 1.0)
00043         spread = 1.0;
00044     spread *= torad;
00045     cosSpread = cos(spread);
00046     VectorSubtract(target, ent->origin, dtarget);
00047     VectorNormalize(dtarget);
00048     VectorScale(dtarget, PLAYER_WIDTH / spread, back);
00049 
00050     while ((check = G_EdictsGetNextLivingActorOfTeam(check, ent->team))) {
00051         if (ent != check) {
00052             /* found ally */
00053             VectorSubtract(check->origin, ent->origin, dcheck);
00054             if (DotProduct(dtarget, dcheck) > 0.0) {
00055                 /* ally in front of player */
00056                 VectorAdd(dcheck, back, dcheck);
00057                 VectorNormalize(dcheck);
00058                 if (DotProduct(dtarget, dcheck) > cosSpread)
00059                     return qtrue;
00060             }
00061         }
00062     }
00063 
00064     /* no ally in danger */
00065     return qfalse;
00066 }
00067 
00073 static qboolean AI_FighterCheckShoot (const edict_t* ent, const edict_t* check, const fireDef_t* fd, float *dist)
00074 {
00075     /* check range */
00076     *dist = VectorDist(ent->origin, check->origin);
00077     if (*dist > fd->range)
00078         return qfalse;
00079     /* don't shoot - we are to close */
00080     else if (*dist < fd->splrad)
00081         return qfalse;
00082 
00083     /* check FF */
00084     if (!G_IsInsane(ent) && AI_CheckFF(ent, check->origin, fd->spread[0]))
00085         return qfalse;
00086 
00087     return qtrue;
00088 }
00089 
00099 qboolean AI_CheckUsingDoor (const edict_t *ent, const edict_t *door)
00100 {
00101     /* don't try to use the door in every case */
00102     if (frand() < 0.3)
00103         return qfalse;
00104 
00105     /* not in the view frustum - don't use the door while not seeing it */
00106     if (!G_FrustumVis(door, ent->origin))
00107         return qfalse;
00108 
00109     /* if the alien is trying to hide and the door is
00110     * still opened, close it */
00111     if (ent->hiding && door->doorState == STATE_OPENED)
00112         return qtrue;
00113 
00114     /* aliens and civilians need different handling */
00115     switch (ent->team) {
00116     case TEAM_ALIEN: {
00117         /* only use the door when there is no civilian or phalanx to kill */
00118         edict_t *check = NULL;
00119 
00120         /* see if there are enemies */
00121         while ((check = G_EdictsGetNextLivingActor(check))) {
00122             float actorVis;
00123             /* don't check for aliens */
00124             if (check->team == ent->team)
00125                 continue;
00126             /* check whether the origin of the enemy is inside the
00127              * AI actors view frustum */
00128             if (!G_FrustumVis(check, ent->origin))
00129                 continue;
00130             /* check whether the enemy is close enough to change the state */
00131             if (VectorDist(check->origin, ent->origin) > MAX_SPOT_DIST)
00132                 continue;
00133             actorVis = G_ActorVis(check->origin, ent, qtrue);
00134             /* there is a visible enemy, don't use that door */
00135             if (actorVis > ACTOR_VIS_0)
00136                 return qfalse;
00137         }
00138         }
00139         break;
00140     case TEAM_CIVILIAN:
00141         /* don't use any door if no alien is inside the viewing angle  - but
00142          * try to hide behind the door when there is an alien */
00143         break;
00144     default:
00145         gi.DPrintf("Invalid team in AI_CheckUsingDoor: %i for ent type: %i\n",
00146             ent->team, ent->type);
00147         break;
00148     }
00149     return qtrue;
00150 }
00151 
00157 static qboolean AI_CheckCrouch (const edict_t *ent)
00158 {
00159     edict_t *check = NULL;
00160 
00161     /* see if we are very well visible by an enemy */
00162     while ((check = G_EdictsGetNextLivingActor(check))) {
00163         float actorVis;
00164         /* don't check for civilians or aliens */
00165         if (check->team == ent->team || G_IsCivilian(check))
00166             continue;
00167         /* check whether the origin of the enemy is inside the
00168          * AI actors view frustum */
00169         if (!G_FrustumVis(check, ent->origin))
00170             continue;
00171         /* check whether the enemy is close enough to change the state */
00172         if (VectorDist(check->origin, ent->origin) > MAX_SPOT_DIST)
00173             continue;
00174         actorVis = G_ActorVis(check->origin, ent, qtrue);
00175         if (actorVis >= ACTOR_VIS_50)
00176             return qtrue;
00177     }
00178     return qfalse;
00179 }
00180 
00187 static qboolean AI_HideNeeded (const edict_t *ent)
00188 {
00189     /* only brave aliens are trying to stay on the field if no dangerous actor is visible */
00190     if (ent->morale > mor_brave->integer) {
00191         edict_t *from = NULL;
00192         /* test if check is visible */
00193         while ((from = G_EdictsGetNextLivingActor(from))) {
00194             if (from->team == ent->team)
00195                 continue;
00196 
00197             if (G_IsCivilian(from))
00198                 continue;
00199 
00200             if (G_IsVisibleForTeam(from, ent->team)) {
00201                 const invList_t *invlist = RIGHT(from);
00202                 const fireDef_t *fd = NULL;
00203                 if (invlist && invlist->item.t) {
00204                     fd = FIRESH_FiredefForWeapon(&invlist->item);
00205                 } else {
00206                     invlist = LEFT(from);
00207                     if (invlist && invlist->item.t)
00208                         fd = FIRESH_FiredefForWeapon(&invlist->item);
00209                 }
00210                 /* search the (visible) inventory (by just checking the weapon in the hands of the enemy */
00211                 if (fd != NULL && fd->range * fd->range >= VectorDistSqr(ent->origin, from->origin)) {
00212                     const int damage = max(0, fd->damage[0] + (fd->damage[1] * crand()));
00213                     if (damage >= ent->HP / 3) {
00214                         const int hidingTeam = AI_GetHidingTeam(ent);
00215                         /* now check whether this enemy is visible for this alien */
00216                         if (G_Vis(hidingTeam, ent, from, VT_NOFRUSTUM))
00217                             return qtrue;
00218                     }
00219                 }
00220             }
00221         }
00222         return qfalse;
00223     }
00224     return qtrue;
00225 }
00226 
00233 static inline const item_t* AI_GetItemFromInventory (const invList_t *ic)
00234 {
00235     if (ic != NULL) {
00236         const item_t *item = &ic->item;
00237         if (item->m && item->t->weapon && (!item->t->reload || item->a > 0))
00238             return item;
00239     }
00240     return NULL;
00241 }
00242 
00250 const item_t *AI_GetItemForShootType (shoot_types_t shootType, const edict_t *ent)
00251 {
00252     /* optimization: reaction fire is automatic */
00253     if (IS_SHOT_REACTION(shootType))
00254         return NULL;
00255 
00256     /* check that the current selected shoot type also has a valid item in its
00257      * corresponding hand slot of the inventory. */
00258     if (IS_SHOT_RIGHT(shootType)) {
00259         const invList_t *ic = RIGHT(ent);
00260         return AI_GetItemFromInventory(ic);
00261     } else if (IS_SHOT_LEFT(shootType)) {
00262         const invList_t *ic = LEFT(ent);
00263         return AI_GetItemFromInventory(ic);
00264     } else if (IS_SHOT_HEADGEAR(shootType)) {
00265         return NULL;
00266     }
00267 
00268     return NULL;
00269 }
00270 
00279 int AI_GetHidingTeam (const edict_t *ent)
00280 {
00281     if (G_IsCivilian(ent))
00282         return TEAM_ALIEN;
00283     return -ent->team;
00284 }
00285 
00295 qboolean AI_FindHidingLocation (int team, edict_t *ent, const pos3_t from, int *tuLeft)
00296 {
00297     /* We need a local table to calculate the hiding steps */
00298     static pathing_t hidePathingTable;
00299     byte minX, maxX, minY, maxY;
00300     const byte crouchingState = G_IsCrouched(ent) ? 1 : 0;
00301     const int distance = min(*tuLeft, HIDE_DIST * 2);
00302 
00303     /* search hiding spot */
00304     G_MoveCalcLocal(&hidePathingTable, 0, ent, from, crouchingState, distance);
00305     ent->pos[2] = from[2];
00306     minX = max(from[0] - HIDE_DIST, 0);
00307     minY = max(from[1] - HIDE_DIST, 0);
00308     maxX = min(from[0] + HIDE_DIST, PATHFINDING_WIDTH - 1);
00309     maxY = min(from[1] + HIDE_DIST, PATHFINDING_WIDTH - 1);
00310 
00311     for (ent->pos[1] = minY; ent->pos[1] <= maxY; ent->pos[1]++) {
00312         for (ent->pos[0] = minX; ent->pos[0] <= maxX; ent->pos[0]++) {
00313             /* time */
00314             const pos_t delta = gi.MoveLength(&hidePathingTable, ent->pos, crouchingState, qfalse);
00315             if (delta > *tuLeft || delta == ROUTING_NOT_REACHABLE)
00316                 continue;
00317 
00318             /* visibility */
00319             G_EdictCalcOrigin(ent);
00320             if (G_TestVis(team, ent, VT_PERISH | VT_NOFRUSTUM) & VIS_YES)
00321                 continue;
00322 
00323             *tuLeft -= delta;
00324             return qtrue;
00325         }
00326     }
00327 
00328     return qfalse;
00329 }
00330 
00340 qboolean AI_FindHerdLocation (edict_t *ent, const pos3_t from, const vec3_t target, int tu)
00341 {
00342     static pathing_t hidePathingTable;
00343     byte minX, maxX, minY, maxY;
00344     const byte crouchingState = G_IsCrouched(ent) ? 1 : 0;
00345     const int distance = min(tu, HERD_DIST * 2);
00346     vec_t length;
00347     vec_t bestlength = 0.0f;
00348     pos3_t bestpos;
00349     edict_t* next = NULL;
00350     edict_t* enemy = NULL;
00351     vec3_t vfriend, venemy;
00352 
00353     /* find the nearest enemy actor to the target*/
00354     while ((next = G_EdictsGetNextLivingActorOfTeam(next, AI_GetHidingTeam(ent)))) {
00355         length = VectorDistSqr(target, next->origin);
00356         if (!bestlength || length < bestlength) {
00357             enemy = next;
00358             bestlength = length;
00359         }
00360     }
00361     assert(enemy);
00362 
00363     /* calculate move table */
00364     G_MoveCalcLocal(&hidePathingTable, 0, ent, from, crouchingState, distance);
00365     ent->pos[2] = from[2];
00366     minX = max(from[0] - HERD_DIST, 0);
00367     minY = max(from[1] - HERD_DIST, 0);
00368     maxX = min(from[0] + HERD_DIST, PATHFINDING_WIDTH - 1);
00369     maxY = min(from[1] + HERD_DIST, PATHFINDING_WIDTH - 1);
00370 
00371     /* search the location */
00372     VectorCopy(from, bestpos);
00373     bestlength = VectorDistSqr(target, ent->origin);
00374     for (ent->pos[1] = minY; ent->pos[1] <= maxY; ent->pos[1]++) {
00375         for (ent->pos[0] = minX; ent->pos[0] <= maxX; ent->pos[0]++) {
00376             /* time */
00377             const pos_t delta = gi.MoveLength(&hidePathingTable, ent->pos, crouchingState, qfalse);
00378             if (delta > tu || delta == ROUTING_NOT_REACHABLE)
00379                 continue;
00380 
00381             G_EdictCalcOrigin(ent);
00382             length = VectorDistSqr(ent->origin, target);
00383             if (length < bestlength) {
00384                 /* check this position to locate behind target from enemy */
00385                 VectorSubtract(target, ent->origin, vfriend);
00386                 VectorNormalize(vfriend);
00387                 VectorSubtract(enemy->origin, ent->origin, venemy);
00388                 VectorNormalize(venemy);
00389                 if (DotProduct(vfriend, venemy) > 0.5) {
00390                     bestlength = length;
00391                     VectorCopy(ent->pos, bestpos);
00392                 }
00393             }
00394         }
00395     }
00396 
00397     if (!VectorCompare(from, bestpos)) {
00398         VectorCopy(bestpos, ent->pos);
00399         return qtrue;
00400     }
00401 
00402     return qfalse;
00403 }
00404 
00411 static edict_t *AI_SearchDestroyableObject (const edict_t *ent, const fireDef_t *fd)
00412 {
00413 #if 0
00414     /* search best none human target */
00415     edict_t *check = NULL;
00416     float dist;
00417 
00418     while ((check = G_EdictsGetNextInUse(check))) {
00419         if (G_IsBreakable(check)) {
00420             float vis;
00421 
00422             if (!AI_FighterCheckShoot(ent, check, fd, &dist))
00423                 continue;
00424 
00425             /* check whether target is visible enough */
00426             vis = G_ActorVis(ent->origin, check, qtrue);
00427             if (vis < ACTOR_VIS_0)
00428                 continue;
00429 
00430             /* take the first best breakable or door and try to shoot it */
00431             return check;
00432         }
00433     }
00434 #endif
00435     return NULL;
00436 }
00437 
00441 static void AI_SearchBestTarget (aiAction_t *aia, const edict_t *ent, edict_t *check, const item_t *item, shoot_types_t shootType, int tu, float *maxDmg, int *bestTime, const fireDef_t *fdArray)
00442 {
00443     const objDef_t *ad;
00444     qboolean visChecked = qfalse;   /* only check visibily once for an actor */
00445     fireDefIndex_t fdIdx;
00446     float dist;
00447 
00448     for (fdIdx = 0; fdIdx < item->m->numFiredefs[fdArray->weapFdsIdx]; fdIdx++) {
00449         const fireDef_t *fd = &fdArray[fdIdx];
00450         const float nspread = SPREAD_NORM((fd->spread[0] + fd->spread[1]) * 0.5 +
00451             GET_ACC(ent->chr.score.skills[ABILITY_ACCURACY], fd->weaponSkill));
00452         /* how many shoots can this actor do */
00453         const int shots = tu / fd->time;
00454         if (shots) {
00455             float dmg;
00456             float vis = ACTOR_VIS_0;
00457             if (!AI_FighterCheckShoot(ent, check, fd, &dist))
00458                 continue;
00459 
00460             /* check how good the target is visible */
00461             if (!visChecked) {  /* only do this once per actor ! */
00462                 vis = G_ActorVis(ent->origin, check, qtrue);
00463                 visChecked = qtrue;
00464             }
00465             if (vis == ACTOR_VIS_0)
00466                 continue;
00467 
00468             /* calculate expected damage */
00469             dmg = vis * (fd->damage[0] + fd->spldmg[0]) * fd->shots * shots;
00470             if (nspread && dist > nspread)
00471                 dmg *= nspread / dist;
00472 
00473             /* take into account armour */
00474             if (CONTAINER(check, gi.csi->idArmour)) {
00475                 ad = CONTAINER(check, gi.csi->idArmour)->item.t;
00476                 dmg *= 1.0 - ad->protection[ad->dmgtype] * 0.01;
00477             }
00478 
00479             if (dmg > check->HP && G_IsReaction(check))
00480                 /* reaction shooters eradication bonus */
00481                 dmg = check->HP + GUETE_KILL + GUETE_REACTION_ERADICATION;
00482             else if (dmg > check->HP)
00483                 /* standard kill bonus */
00484                 dmg = check->HP + GUETE_KILL;
00485 
00486             /* ammo is limited and shooting gives away your position */
00487             if ((dmg < 25.0 && vis < 0.2) /* too hard to hit */
00488                 || (dmg < 10.0 && vis < 0.6) /* uber-armour */
00489                 || dmg < 0.1) /* at point blank hit even with a stick */
00490                 continue;
00491 
00492             /* civilian malus */
00493             if (G_IsCivilian(check) && !G_IsInsane(ent))
00494                 dmg *= GUETE_CIV_FACTOR;
00495 
00496             /* add random effects */
00497             if (dmg > 0)
00498                 dmg += GUETE_RANDOM * frand();
00499 
00500             /* check if most damage can be done here */
00501             if (dmg > *maxDmg) {
00502                 *maxDmg = dmg;
00503                 *bestTime = fd->time * shots;
00504                 aia->shootType = shootType;
00505                 aia->shots = shots;
00506                 aia->target = check;
00507                 aia->fd = fd;
00508             }
00509 
00510             if (!aia->target) {
00511                 aia->target = AI_SearchDestroyableObject(ent, fd);
00512                 if (aia->target) {
00513                     /* don't take vis into account, don't multiply with amount of shots
00514                      * others (human victims) should be preferred, that's why we don't
00515                      * want a too high value here */
00516                     *maxDmg = (fd->damage[0] + fd->spldmg[0]);
00517                     *bestTime = fd->time * shots;
00518                     aia->shootType = shootType;
00519                     aia->shots = shots;
00520                     aia->fd = fd;
00521                 }
00522             }
00523         }
00524     }
00525 }
00526 
00532 static float AI_FighterCalcBestAction (edict_t * ent, pos3_t to, aiAction_t * aia)
00533 {
00534     edict_t *check = NULL;
00535     int tu;
00536     pos_t move;
00537     shoot_types_t shootType;
00538     float minDist;
00539     float bestActionPoints, maxDmg;
00540     int bestTime = -1;
00541 
00542     move = gi.MoveLength(&level.pathingMap, to,
00543             G_IsCrouched(ent) ? 1 : 0, qtrue);
00544     tu = ent->TU - move;
00545 
00546     /* test for time */
00547     if (tu < 0 || move == ROUTING_NOT_REACHABLE)
00548         return AI_ACTION_NOTHING_FOUND;
00549 
00550     bestActionPoints = 0.0;
00551     memset(aia, 0, sizeof(*aia));
00552 
00553     /* set basic parameters */
00554     VectorCopy(to, aia->to);
00555     VectorCopy(to, aia->stop);
00556     G_EdictSetOrigin(ent, to);
00557 
00558     maxDmg = 0.0;
00559     /* search best target */
00560     while ((check = G_EdictsGetNextLivingActor(check))) {
00561         if (ent != check && (check->team != ent->team || G_IsInsane(ent))) {
00562             /* don't shoot civilians in mp */
00563             if (G_IsCivilian(check) && sv_maxclients->integer > 1 && !G_IsInsane(ent))
00564                 continue;
00565 
00566             /* shooting */
00567             maxDmg = 0.0;
00568             for (shootType = ST_RIGHT; shootType < ST_NUM_SHOOT_TYPES; shootType++) {
00569                 const item_t *item;
00570                 const fireDef_t *fdArray;
00571 
00572                 item = AI_GetItemForShootType(shootType, ent);
00573                 if (!item)
00574                     continue;
00575 
00576                 fdArray = FIRESH_FiredefForWeapon(item);
00577                 if (fdArray == NULL)
00578                     continue;
00579 
00580                 AI_SearchBestTarget(aia, ent, check, item, shootType, tu, &maxDmg, &bestTime, fdArray);
00581             }
00582         } /* firedefs */
00583     }
00584     /* add damage to bestActionPoints */
00585     if (aia->target) {
00586         bestActionPoints += maxDmg;
00587         assert(bestTime > 0);
00588         tu -= bestTime;
00589     }
00590 
00591     if (!G_IsRaged(ent)) {
00592         const int hidingTeam = AI_GetHidingTeam(ent);
00593         /* hide */
00594         if (!AI_HideNeeded(ent) || !(G_TestVis(hidingTeam, ent, VT_PERISH | VT_NOFRUSTUM) & VIS_YES)) {
00595             /* is a hiding spot */
00596             bestActionPoints += GUETE_HIDE + (aia->target ? GUETE_CLOSE_IN : 0);
00597         } else if (aia->target && tu >= TU_MOVE_STRAIGHT) {
00598             /* reward short walking to shooting spot, when seen by enemies; */
00605             bestActionPoints += max(GUETE_CLOSE_IN - move, 0);
00606 
00607             if (!AI_FindHidingLocation(hidingTeam, ent, to, &tu)) {
00608                 /* nothing found */
00609                 G_EdictSetOrigin(ent, to);
00611             } else {
00612                 /* found a hiding spot */
00613                 VectorCopy(ent->pos, aia->stop);
00614                 bestActionPoints += GUETE_HIDE;
00617             }
00618         }
00619     }
00620 
00621     /* reward closing in */
00622     minDist = CLOSE_IN_DIST;
00623     check = NULL;
00624     while ((check = G_EdictsGetNextLivingActor(check))) {
00625         if (check->team != ent->team) {
00626             const float dist = VectorDist(ent->origin, check->origin);
00627             minDist = min(dist, minDist);
00628         }
00629     }
00630     bestActionPoints += GUETE_CLOSE_IN * (1.0 - minDist / CLOSE_IN_DIST);
00631 
00632     return bestActionPoints;
00633 }
00634 
00643 static float AI_CivilianCalcBestAction (edict_t * ent, pos3_t to, aiAction_t * aia)
00644 {
00645     edict_t *check = NULL;
00646     int tu;
00647     pos_t move;
00648     float minDist, minDistCivilian, minDistFighter;
00649     float bestActionPoints;
00650     float reactionTrap = 0.0;
00651     float delta = 0.0;
00652     const byte crouchingState = G_IsCrouched(ent) ? 1 : 0;
00653 
00654     /* set basic parameters */
00655     bestActionPoints = 0.0;
00656     memset(aia, 0, sizeof(*aia));
00657     VectorCopy(to, aia->to);
00658     VectorCopy(to, aia->stop);
00659     G_EdictSetOrigin(ent, to);
00660 
00661     move = gi.MoveLength(&level.pathingMap, to, crouchingState, qtrue);
00662     tu = ent->TU - move;
00663 
00664     /* test for time */
00665     if (tu < 0 || move == ROUTING_NOT_REACHABLE)
00666         return AI_ACTION_NOTHING_FOUND;
00667 
00668     /* check whether this civilian can use weapons */
00669     if (ent->chr.teamDef) {
00670         const teamDef_t* teamDef = ent->chr.teamDef;
00671         if (!G_IsPaniced(ent) && teamDef->weapons)
00672             return AI_FighterCalcBestAction(ent, to, aia);
00673     } else
00674         gi.DPrintf("AI_CivilianCalcBestAction: Error - civilian team with no teamdef\n");
00675 
00676     /* run away */
00677     minDist = minDistCivilian = minDistFighter = RUN_AWAY_DIST * UNIT_SIZE;
00678 
00679     while ((check = G_EdictsGetNextLivingActor(check))) {
00680         float dist;
00681         if (ent == check)
00682             continue;
00683         dist = VectorDist(ent->origin, check->origin);
00684         /* if we are trying to walk to a position that is occupied by another actor already we just return */
00685         if (!dist)
00686             return AI_ACTION_NOTHING_FOUND;
00687         switch (check->team) {
00688         case TEAM_ALIEN:
00689             if (dist < minDist)
00690                 minDist = dist;
00691             break;
00692         case TEAM_CIVILIAN:
00693             if (dist < minDistCivilian)
00694                 minDistCivilian = dist;
00695             break;
00696         case TEAM_PHALANX:
00697             if (dist < minDistFighter)
00698                 minDistFighter = dist;
00699             break;
00700         }
00701     }
00702 
00703     minDist /= UNIT_SIZE;
00704     minDistCivilian /= UNIT_SIZE;
00705     minDistFighter /= UNIT_SIZE;
00706 
00707     if (minDist < 8.0) {
00708         /* very near an alien: run away fast */
00709         delta = 4.0 * minDist;
00710     } else if (minDist < 16.0) {
00711         /* near an alien: run away */
00712         delta = 24.0 + minDist;
00713     } else if (minDist < 24.0) {
00714         /* near an alien: run away slower */
00715         delta = 40.0 + (minDist - 16) / 4;
00716     } else {
00717         delta = 42.0;
00718     }
00719     /* near a civilian: join him (1/3) */
00720     if (minDistCivilian < 10.0)
00721         delta += (10.0 - minDistCivilian) / 3.0;
00722     /* near a fighter: join him (1/5) */
00723     if (minDistFighter < 15.0)
00724         delta += (15.0 - minDistFighter) / 5.0;
00725     /* don't go close to a fighter to let him move */
00726     if (minDistFighter < 2.0)
00727         delta /= 10.0;
00728 
00729     /* try to hide */
00730     check = NULL;
00731     while ((check = G_EdictsGetNextLivingActor(check))) {
00732         if (ent == check)
00733             continue;
00734         if (!(G_IsAlien(check) || G_IsInsane(ent)))
00735             continue;
00736 
00737         if (!G_IsVisibleForTeam(check, ent->team))
00738             continue;
00739 
00740         if (G_ActorVis(check->origin, ent, qtrue) > 0.25)
00741             reactionTrap += 25.0;
00742     }
00743     delta -= reactionTrap;
00744     bestActionPoints += delta;
00745 
00746     /* add laziness */
00747     if (ent->TU)
00748         bestActionPoints += GUETE_CIV_LAZINESS * tu / ent->TU;
00749     /* add random effects */
00750     bestActionPoints += GUETE_CIV_RANDOM * frand();
00751 
00752     return bestActionPoints;
00753 }
00754 
00761 static int AI_CheckForMissionTargets (const player_t* player, edict_t *ent, aiAction_t *aia)
00762 {
00763     int bestActionPoints = AI_ACTION_NOTHING_FOUND;
00764     const byte crouchingState = G_IsCrouched(ent) ? 1 : 0;
00765 
00766     /* reset any previous given action set */
00767     memset(aia, 0, sizeof(*aia));
00768 
00769     if (ent->team == TEAM_CIVILIAN) {
00770         edict_t *checkPoint = NULL;
00771         int length;
00772         int i = 0;
00773         /* find waypoints in a closer distance - if civilians are not close enough, let them walk
00774          * around until they came close */
00775         for (checkPoint = ai_waypointList; checkPoint != NULL; checkPoint = checkPoint->groupChain) {
00776             if (checkPoint->inuse)
00777                 continue;
00778 
00779             /* the lower the count value - the nearer the final target */
00780             if (checkPoint->count < ent->count) {
00781                 if (VectorDist(ent->origin, checkPoint->origin) <= WAYPOINT_CIV_DIST) {
00782                     const pos_t move = gi.MoveLength(&level.pathingMap, checkPoint->pos, crouchingState, qtrue);
00783                     i++;
00784                     if (move == ROUTING_NOT_REACHABLE)
00785                         continue;
00786 
00787                     /* test for time and distance */
00788                     length = ent->TU - move;
00789                     bestActionPoints = GUETE_MISSION_TARGET + length;
00790 
00791                     ent->count = checkPoint->count;
00792                     VectorCopy(checkPoint->pos, aia->to);
00793                 }
00794             }
00795         }
00796         /* reset the count value for this civilian to restart the search */
00797         if (!i)
00798             ent->count = 100;
00799     } else if (ent->team == TEAM_ALIEN) {
00800         /* search for a mission edict */
00801         edict_t *mission = NULL;
00802         while ((mission = G_EdictsGetNextInUse(mission))) {
00803             if (mission->type == ET_MISSION) {
00804                 VectorCopy(mission->pos, aia->to);
00805                 aia->target = mission;
00806                 if (player->pers.team == mission->team) {
00807                     bestActionPoints = GUETE_MISSION_TARGET;
00808                     break;
00809                 } else {
00810                     /* try to prevent the phalanx from reaching their mission target */
00811                     bestActionPoints = GUETE_MISSION_OPPONENT_TARGET;
00812                 }
00813             }
00814         }
00815     }
00816 
00817     return bestActionPoints;
00818 }
00819 
00820 #define AI_MAX_DIST 30
00821 
00827 static aiAction_t AI_PrepBestAction (const player_t *player, edict_t * ent)
00828 {
00829     aiAction_t aia, bestAia;
00830     pos3_t oldPos, to;
00831     vec3_t oldOrigin;
00832     int xl, yl, xh, yh;
00833     int dist;
00834     float bestActionPoints, best;
00835     const byte crouchingState = G_IsCrouched(ent) ? 1 : 0;
00836 
00837     /* calculate move table */
00838     G_MoveCalc(0, ent, ent->pos, crouchingState, ent->TU);
00839     Com_DPrintf(DEBUG_ENGINE, "AI_PrepBestAction: Called MoveMark.\n");
00840     gi.MoveStore(&level.pathingMap);
00841 
00842     /* set borders */
00843     dist = (ent->TU + 1) / 2;
00844     xl = max((int) ent->pos[0] - dist, 0);
00845     yl = max((int) ent->pos[1] - dist, 0);
00846     xh = min((int) ent->pos[0] + dist, PATHFINDING_WIDTH);
00847     yh = min((int) ent->pos[1] + dist, PATHFINDING_WIDTH);
00848 
00849     /* search best action */
00850     best = AI_ACTION_NOTHING_FOUND;
00851     VectorCopy(ent->pos, oldPos);
00852     VectorCopy(ent->origin, oldOrigin);
00853 
00854     /* evaluate moving to every possible location in the search area,
00855      * including combat considerations */
00856     for (to[2] = 0; to[2] < PATHFINDING_HEIGHT; to[2]++)
00857         for (to[1] = yl; to[1] < yh; to[1]++)
00858             for (to[0] = xl; to[0] < xh; to[0]++) {
00859                 const pos_t move = gi.MoveLength(&level.pathingMap, to, crouchingState, qtrue);
00860                 if (move != ROUTING_NOT_REACHABLE && move <= ent->TU) {
00861                     if (G_IsCivilian(ent) || G_IsPaniced(ent))
00862                         bestActionPoints = AI_CivilianCalcBestAction(ent, to, &aia);
00863                     else
00864                         bestActionPoints = AI_FighterCalcBestAction(ent, to, &aia);
00865 
00866                     if (bestActionPoints > best) {
00867                         bestAia = aia;
00868                         best = bestActionPoints;
00869                     }
00870                 }
00871             }
00872 
00873     VectorCopy(oldPos, ent->pos);
00874     VectorCopy(oldOrigin, ent->origin);
00875 
00876     bestActionPoints = AI_CheckForMissionTargets(player, ent, &aia);
00877     if (bestActionPoints > best) {
00878         bestAia = aia;
00879         best = bestActionPoints;
00880     }
00881 
00882     /* nothing found to do */
00883     if (best == AI_ACTION_NOTHING_FOUND) {
00884         bestAia.target = NULL;
00885         return bestAia;
00886     }
00887 
00888     /* check if the actor is in crouched state and try to stand up before doing the move */
00889     if (G_IsCrouched(ent))
00890         G_ClientStateChange(player, ent, STATE_CROUCHED, qtrue);
00891 
00892     /* do the move */
00893     G_ClientMove(player, 0, ent, bestAia.to);
00894 
00895     /* test for possible death during move. reset bestAia due to dead status */
00896     if (G_IsDead(ent))
00897         memset(&bestAia, 0, sizeof(bestAia));
00898 
00899     return bestAia;
00900 }
00901 
00902 edict_t *ai_waypointList;
00903 
00904 void G_AddToWayPointList (edict_t *ent)
00905 {
00906     int i = 1;
00907 
00908     if (!ai_waypointList)
00909         ai_waypointList = ent;
00910     else {
00911         edict_t *e = ai_waypointList;
00912         while (e->groupChain) {
00913             e = e->groupChain;
00914             i++;
00915         }
00916         i++;
00917         e->groupChain = ent;
00918     }
00919 }
00920 
00927 void AI_TurnIntoDirection (edict_t *ent, const pos3_t pos)
00928 {
00929     int dv;
00930     const byte crouchingState = G_IsCrouched(ent) ? 1 : 0;
00931 
00932     G_MoveCalc(ent->team, ent, pos, crouchingState, ent->TU);
00933 
00934     dv = gi.MoveNext(&level.pathingMap, pos, crouchingState);
00935     if (dv != ROUTING_UNREACHABLE) {
00936         const byte dir = getDVdir(dv);
00937         /* Only attempt to turn if the direction is not a vertical only action */
00938         if (dir < CORE_DIRECTIONS || dir >= FLYING_DIRECTIONS)
00939             G_ActorDoTurn(ent, dir & (CORE_DIRECTIONS - 1));
00940     }
00941 }
00942 
00946 static void AI_TryToReloadWeapon (edict_t *ent, containerIndex_t containerID)
00947 {
00948     if (G_ClientCanReload(ent, containerID)) {
00949         G_ActorReload(ent, INVDEF(containerID));
00950     } else {
00951         G_ActorInvMove(ent, INVDEF(containerID), CONTAINER(ent, containerID), INVDEF(gi.csi->idFloor), NONE, NONE, qtrue);
00952     }
00953 }
00954 
00964 void AI_ActorThink (player_t * player, edict_t * ent)
00965 {
00966     aiAction_t bestAia;
00967 
00968     /* if a weapon can be reloaded we attempt to do so if TUs permit, otherwise drop it */
00969     if (!G_IsPaniced(ent)) {
00970         if (RIGHT(ent) && RIGHT(ent)->item.t->reload && RIGHT(ent)->item.a == 0)
00971             AI_TryToReloadWeapon(ent, gi.csi->idRight);
00972         if (LEFT(ent) && LEFT(ent)->item.t->reload && LEFT(ent)->item.a == 0)
00973             AI_TryToReloadWeapon(ent, gi.csi->idLeft);
00974     }
00975 
00976     /* if both hands are empty, attempt to get a weapon out of backpack or the
00977      * floor (if TUs permit) */
00978     if (!LEFT(ent) && !RIGHT(ent))
00979         G_ClientGetWeaponFromInventory(ent);
00980 
00981     bestAia = AI_PrepBestAction(player, ent);
00982 
00983     /* shoot and hide */
00984     if (bestAia.target) {
00985         const fireDefIndex_t fdIdx = bestAia.fd ? bestAia.fd->fdIdx : 0;
00986         /* shoot until no shots are left or target died */
00987         while (bestAia.shots) {
00988             G_ClientShoot(player, ent, bestAia.target->pos, bestAia.shootType, fdIdx, NULL, qtrue, bestAia.z_align);
00989             bestAia.shots--;
00990             /* died by our own shot? */
00991             if (G_IsDead(ent))
00992                 return;
00993             /* check for target's death */
00994             if (G_IsDead(bestAia.target)) {
00995                 /* search another target now */
00996                 bestAia = AI_PrepBestAction(player, ent);
00997                 /* no other target found - so no need to hide */
00998                 if (!bestAia.target)
00999                     return;
01000             }
01001         }
01002         ent->hiding = qtrue;
01003 
01004         /* now hide - for this we use the team of the alien actor because a phalanx soldier
01005          * might become visible during the hide movement */
01006         G_ClientMove(player, ent->team, ent, bestAia.stop);
01007         /* no shots left, but possible targets left - maybe they shoot back
01008          * or maybe they are still close after hiding */
01009 
01010         /* decide whether the actor maybe wants to go crouched */
01011         if (AI_CheckCrouch(ent))
01012             G_ClientStateChange(player, ent, STATE_CROUCHED, qfalse);
01013 
01014         /* actor is still alive - try to turn into the appropriate direction to see the target
01015          * actor once he sees the ai, too */
01016         AI_TurnIntoDirection(ent, bestAia.target->pos);
01017 
01020         /* G_ClientStateChange(player, ent->number, STATE_REACTION_ONCE, qtrue); */
01021 
01022         ent->hiding = qfalse;
01023     }
01024 }
01025 
01026 
01031 void AI_Run (void)
01032 {
01033     player_t *player;
01034     int i;
01035 
01036     /* don't run this too often to prevent overflows */
01037     if (level.framenum % 10)
01038         return;
01039 
01040     /* set players to ai players and cycle over all of them */
01041     for (i = 0, player = game.players + game.sv_maxplayersperteam; i < game.sv_maxplayersperteam; i++, player++)
01042         if (player->inuse && G_IsAIPlayer(player) && level.activeTeam == player->pers.team) {
01043             /* find next actor to handle */
01044             edict_t *ent = player->pers.last;
01045 
01046             while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, player->pers.team))) {
01047                 if (ent->TU) {
01048                     if (g_ailua->integer)
01049                         AIL_ActorThink(player, ent);
01050                     else
01051                         AI_ActorThink(player, ent);
01052                     player->pers.last = ent;
01053                     return;
01054                 }
01055             }
01056 
01057             /* nothing left to do, request endround */
01058             G_ClientEndRound(player);
01059             player->pers.last = NULL;
01060             return;
01061         }
01062 }
01063 
01068 static void AI_SetStats (edict_t * ent)
01069 {
01070     CHRSH_CharGenAbilitySkills(&ent->chr, sv_maxclients->integer >= 2);
01071 
01072     ent->HP = ent->chr.HP;
01073     ent->morale = ent->chr.morale;
01074     ent->STUN = 0;
01075 
01076     G_ActorGiveTimeUnits(ent);
01077 }
01078 
01079 
01085 static void AI_SetCharacterValues (edict_t * ent, int team)
01086 {
01087     /* Set model. */
01088     const char *teamDefintion;
01089     if (team != TEAM_CIVILIAN) {
01090         if (gi.csi->numAlienTeams) {
01091             const int alienTeam = rand() % gi.csi->numAlienTeams;
01092             assert(gi.csi->alienTeams[alienTeam]);
01093             teamDefintion = gi.csi->alienTeams[alienTeam]->id;
01094         } else
01095             teamDefintion = gi.Cvar_String("ai_alien");
01096     } else if (team == TEAM_CIVILIAN) {
01097         teamDefintion = gi.Cvar_String("ai_civilian");
01098     }
01099     gi.GetCharacterValues(teamDefintion, &ent->chr);
01100     if (!ent->chr.teamDef)
01101         gi.Error("Could not set teamDef for character: '%s'", teamDefintion);
01102 }
01103 
01104 
01110 static void AI_SetEquipment (edict_t * ent, const equipDef_t * ed)
01111 {
01112     /* Pack equipment. */
01113     if (ent->chr.teamDef->weapons)
01114         game.i.EquipActor(&game.i, &ent->chr.i, ed, ent->chr.teamDef);
01115     else if (ent->chr.teamDef)
01116         /* actor cannot handle equipment but no weapons */
01117         game.i.EquipActorMelee(&game.i, &ent->chr.i, ent->chr.teamDef);
01118     else
01119         gi.DPrintf("AI_InitPlayer: actor with no equipment\n");
01120 }
01121 
01122 
01129 static void AI_InitPlayer (const player_t * player, edict_t * ent, const equipDef_t * ed)
01130 {
01131     const int team = player->pers.team;
01132 
01133     /* Set the model and chose alien race. */
01134     AI_SetCharacterValues(ent, team);
01135 
01136     /* Calculate stats. */
01137     AI_SetStats(ent);
01138 
01139     /* Give equipment. */
01140     if (ed != NULL)
01141         AI_SetEquipment(ent, ed);
01142 
01143     /* after equipping the actor we can also get the model indices */
01144     ent->body = gi.ModelIndex(CHRSH_CharGetBody(&ent->chr));
01145     ent->head = gi.ModelIndex(CHRSH_CharGetHead(&ent->chr));
01146 
01147     /* no need to call G_SendStats for the AI - reaction fire is serverside only for the AI */
01148     if (frand() < 0.75f)
01149         G_ClientStateChange(player, ent, STATE_REACTION_ONCE, qfalse);
01150 
01151     /* initialize the LUA AI now */
01152     if (team == TEAM_CIVILIAN)
01153         AIL_InitActor(ent, "civilian", "default");
01154     else if (team == TEAM_ALIEN)
01155         AIL_InitActor(ent, "alien", "default");
01156     else
01157         gi.DPrintf("AI_InitPlayer: unknown team AI\n");
01158 }
01159 
01166 static void G_SpawnAIPlayer (const player_t * player, int numSpawn)
01167 {
01168     const equipDef_t *ed;
01169     int i;
01170     const int team = player->pers.team;
01171 
01172     /* prepare equipment */
01173     if (team != TEAM_CIVILIAN) {
01174         char name[MAX_VAR];
01175         Q_strncpyz(name, gi.Cvar_String("ai_equipment"), sizeof(name));
01176         for (i = 0, ed = gi.csi->eds; i < gi.csi->numEDs; i++, ed++)
01177             if (!strcmp(name, ed->name))
01178                 break;
01179         if (i == gi.csi->numEDs)
01180             ed = &gi.csi->eds[0];
01181     } else {
01182         ed = NULL;
01183     }
01184 
01185     /* spawn players */
01186     for (i = 0; i < numSpawn; i++) {
01187         edict_t *ent = G_ClientGetFreeSpawnPointForActorSize(player, ACTOR_SIZE_NORMAL);
01188         if (!ent) {
01189             gi.DPrintf("Not enough spawn points for team %i\n", team);
01190             break;
01191         }
01192 
01193         /* initialize the new actor */
01194         AI_InitPlayer(player, ent, ed);
01195     }
01196     /* show visible actors */
01197     G_ClearVisFlags(team);
01198     G_CheckVis(NULL, qfalse);
01199 }
01200 
01201 
01209 player_t *AI_CreatePlayer (int team)
01210 {
01211     player_t *p;
01212     int i;
01213 
01214     if (!sv_ai->integer) {
01215         gi.DPrintf("AI deactivated - set sv_ai cvar to 1 to activate it\n");
01216         return NULL;
01217     }
01218 
01219     /* set players to ai players and cycle over all of them */
01220     for (i = 0, p = game.players + game.sv_maxplayersperteam; i < game.sv_maxplayersperteam; i++, p++)
01221         if (!p->inuse) {
01222             memset(p, 0, sizeof(*p));
01223             p->inuse = qtrue;
01224             p->num = p - game.players;
01225             p->pers.ai = qtrue;
01226             G_SetTeamForPlayer(p, team);
01227             if (p->pers.team == TEAM_CIVILIAN)
01228                 G_SpawnAIPlayer(p, ai_numcivilians->integer);
01229             else if (sv_maxclients->integer == 1)
01230                 G_SpawnAIPlayer(p, ai_numaliens->integer);
01231             else
01232                 G_SpawnAIPlayer(p, ai_numactors->integer);
01233             gi.DPrintf("Created AI player (team %i)\n", p->pers.team);
01234             return p;
01235         }
01236 
01237     /* nothing free */
01238     return NULL;
01239 }

Generated by  doxygen 1.6.2