diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index 585fcb54..2ef701b1 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -8,6 +8,7 @@ extern BOOLEAN gfTurnBasedAI; +#define MAX_DIST 160 // THIS IS AN ITEM # - AND FOR NOW JUST COMPLETELY FAKE... #define MAX_TOSS_SEARCH_DIST 1 // must throw within this of opponent @@ -87,7 +88,7 @@ enum #define PERCENT_TO_IGNORE_THREAT 50 // any less, use threat value #define ACTION_TIMEOUT_CYCLES 50 // # failed cycles through AI -#define MAX_THREAT_RANGE 400 // 30 tiles worth +#define MAX_THREAT_RANGE 4000 // 30 tiles worth #define MIN_PERCENT_BETTER 5 // 5% improvement in cover is good @@ -222,3 +223,32 @@ INT16 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ); BOOLEAN ArmySeesOpponents( void ); void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN suppressionFire); + +void AskForNoise( SOLDIERTYPE * pSoldier, INT16 *sNoiseGridno, UINT8 *ubNoiseVolume, INT8 *bNoiseLevel, INT8 bDist ); +INT32 RangeToClosestOpponent( SOLDIERTYPE *pSoldier ); +INT16 FindClimbGridNo( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 bMaxActionPoints ); +//BOOLEAN IsTooFarFromFriends( SOLDIERTYPE *pSoldier ); +INT16 FindBestCoverNearTheGridNo(SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubSearchRadius ); +INT16 FindRoofBetweenGridNos( INT16 sFGridNo, INT16 sSGridNo ); + +INT8 FindDirectionForClimbing( INT16 sGridNo ); + +enum +{ + TM_OUTOFRANGE = 0, + TM_HASFIRED, + TM_TRIED_TO_CLIMB, + TM_TRIED_TO_CLIMBDOWN, + TM_LAST_DEST_GRID, + TM_LOOKED_FOR_COVER, +// TM_LOOKFORROOF, + MAX_NUMBER_OF_TM +}; + +extern INT32 gpTurnMem[ MAX_NUMBER_OF_TM ]; + +UINT32 CountRatingOfTeamMatesDensity( SOLDIERTYPE * pSoldier, INT16 sGridNo, INT32 iDist ); +UINT32 CountRatingOfOpponentsDensity( SOLDIERTYPE * pSoldier, INT16 sGridNo, INT32 iDist ); +UINT8 NeedTimeToReadyGun_IfAlreadyReady( SOLDIERTYPE *pSoldier ); +SOLDIERTYPE* FindFriendFarthestFromEnemies( SOLDIERTYPE * pSoldier ); + diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 2fca8203..fee29d39 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -27,6 +27,8 @@ // InWaterOrGas - gas stuff // RoamingRange - point patrol stuff +extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady ); + UINT8 Urgency[NUM_STATUS_STATES][NUM_MORALE_STATES] = { {URGENCY_LOW, URGENCY_LOW, URGENCY_LOW, URGENCY_LOW, URGENCY_LOW}, // green @@ -47,7 +49,7 @@ UINT16 MovementMode[LAST_MOVEMENT_ACTION + 1][NUM_URGENCY_STATES] = {WALKING, WALKING, WALKING }, // AI_ACTION_POINT_PATROL, {WALKING, RUNNING, RUNNING }, // AI_ACTION_LEAVE_WATER_GAS, - {WALKING, SWATTING, RUNNING }, // AI_ACTION_SEEK_NOISE, + {WALKING, RUNNING, RUNNING }, // AI_ACTION_SEEK_NOISE, {RUNNING, RUNNING, RUNNING }, // AI_ACTION_ESCORTED_MOVE, {WALKING, RUNNING, RUNNING }, // AI_ACTION_RUN_AWAY, @@ -55,7 +57,7 @@ UINT16 MovementMode[LAST_MOVEMENT_ACTION + 1][NUM_URGENCY_STATES] = {WALKING, WALKING, WALKING }, // AI_ACTION_APPROACH_MERC {RUNNING, RUNNING, RUNNING }, // AI_ACTION_TRACK {RUNNING, RUNNING, RUNNING }, // AI_ACTION_EAT - {WALKING, RUNNING, SWATTING}, // AI_ACTION_PICKUP_ITEM + {WALKING, RUNNING, RUNNING}, // AI_ACTION_PICKUP_ITEM {WALKING, WALKING, WALKING}, // AI_ACTION_SCHEDULE_MOVE {WALKING, WALKING, WALKING}, // AI_ACTION_WALK @@ -380,19 +382,16 @@ void NewDest(SOLDIERTYPE *pSoldier, UINT16 usGridNo) { if ( pSoldier->bTeam == ENEMY_TEAM && pSoldier->bAlertStatus == STATUS_RED ) { -/* remove warning (jonathanl) switch( pSoldier->bAction ) { -*/ - /* + case AI_ACTION_MOVE_TO_CLIMB: case AI_ACTION_RUN_AWAY: pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, pSoldier->bAction ); fSet = TRUE; - break;*/ -// remove warning pt2 (jonathanl) -// default: - if ( PreRandom( 5 - SoldierDifficultyLevel( pSoldier ) ) == 0 ) + break; + default: +/* if ( PreRandom( 5 - SoldierDifficultyLevel( pSoldier ) ) == 0 ) { INT16 sClosestNoise = (INT16) MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL ); if ( sClosestNoise != NOWHERE && PythSpacesAway( pSoldier->sGridNo, sClosestNoise ) < MaxDistanceVisible() + 10 ) @@ -400,16 +399,14 @@ void NewDest(SOLDIERTYPE *pSoldier, UINT16 usGridNo) pSoldier->usUIMovementMode = SWATTING; fSet = TRUE; } - } + }*/ if ( !fSet ) { pSoldier->usUIMovementMode = DetermineMovementMode( pSoldier, pSoldier->bAction ); fSet = TRUE; } -/* remove warning pt3 (jonathanl) break; } -*/ } else @@ -436,6 +433,8 @@ void NewDest(SOLDIERTYPE *pSoldier, UINT16 usGridNo) BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier) { INT8 bMinPointsNeeded = 0; + INT8 bAPForStandUp = 0; + INT8 bAPToLookAtWall = ( FindDirectionForClimbing( pSoldier->sGridNo ) == pSoldier->bDirection ) ? 0 : 1; //NumMessage("AffordableAction - Guy#",pSoldier->ubID); @@ -527,11 +526,14 @@ BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier) case AI_ACTION_CLIMB_ROOF: if (pSoldier->bLevel == 0) { - bMinPointsNeeded = AP_CLIMBROOF; + if( PTR_CROUCHED ) bAPForStandUp = 2; + if( PTR_PRONE ) bAPForStandUp = 4; + bMinPointsNeeded = AP_CLIMBROOF + bAPForStandUp + bAPToLookAtWall; } else { - bMinPointsNeeded = AP_CLIMBOFFROOF; + if( !PTR_CROUCHED ) bAPForStandUp = 2; + bMinPointsNeeded = AP_CLIMBOFFROOF + bAPForStandUp + bAPToLookAtWall; } break; @@ -1423,7 +1425,7 @@ INT16 EstimatePathCostToLocation( SOLDIERTYPE * pSoldier, INT16 sDestGridNo, INT { // different buildings! // yes, pass in same gridno twice... want closest climb-down spot for building we are on! - sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, FALSE ); + sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier, sDestGridNo, pSoldier->sGridNo, FALSE ); if (sClimbGridNo == NOWHERE) { sPathCost = 0; @@ -1464,7 +1466,7 @@ INT16 EstimatePathCostToLocation( SOLDIERTYPE * pSoldier, INT16 sDestGridNo, INT else { // got to go DOWN off building - sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier, pSoldier->sGridNo, pSoldier->sGridNo, FALSE ); + sClimbGridNo = FindClosestClimbPointAvailableToAI( pSoldier, sDestGridNo, pSoldier->sGridNo, FALSE ); } if (sClimbGridNo == NOWHERE) @@ -2233,6 +2235,8 @@ INT32 CalcManThreatValue( SOLDIERTYPE *pEnemy, INT16 sMyGrid, UINT8 ubReduceForC INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT16 * pusFromGridNo) { + if( pSoldier->bBleeding > 0 )return MAX_ROAMING_RANGE; + if ( CREATURE_OR_BLOODCAT( pSoldier ) ) { if ( pSoldier->bAlertStatus == STATUS_BLACK ) @@ -2512,30 +2516,166 @@ BOOLEAN ArmySeesOpponents( void ) return( FALSE ); } -#ifdef DEBUGDECISIONS -void AIPopMessage ( STR16 str ) + +void AskForNoise( SOLDIERTYPE * pSoldier, INT16 *sNoiseGridno, UINT8 *ubNoiseVolume, INT8 *bNoiseLevel, INT8 bDist ) { - DebugAI((STR)str); + UINT32 uiLoop; + SOLDIERTYPE *pFriend; + + + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) + { + pFriend = MercSlots[ uiLoop ]; + + // it's me! Next merc + if( pFriend == pSoldier ) + continue; + + // if this merc is inactive, at base, on assignment, dead, unconscious + if (!pFriend || (pFriend->bLife < OKLIFE)) + { + continue; // next merc + } + + if ( pSoldier->bTeam != pFriend->bTeam ) + { + continue; // next merc + } + + if( pFriend->sNoiseGridno != NOWHERE && GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, pFriend->sGridNo ) / 10 <= bDist ) + { + *sNoiseGridno = pFriend->sNoiseGridno; + *ubNoiseVolume = pFriend->ubNoiseVolume; + *bNoiseLevel = pFriend->bNoiseLevel; + return; + } + + } } -void AIPopMessage ( const char* str ) +INT32 RangeToClosestOpponent( SOLDIERTYPE *pSoldier ) { - STR tempstr; - sprintf((CHAR *) tempstr,"%s", str); - DebugAI(tempstr); + INT16 sClosestDisturbance; + //BOOLEAN fClimb; + sClosestDisturbance = ClosestKnownOpponent(pSoldier, 0, 0); + if( sClosestDisturbance != NOWHERE ) + return GetRangeFromGridNoDiff( pSoldier->sGridNo, sClosestDisturbance ); + else + return 128; } -void AINumMessage(const char* str, INT32 num) + +UINT8 NeedTimeToReadyGun_IfAlreadyReady( SOLDIERTYPE *pSoldier ) { - STR tempstr; - sprintf((CHAR *) tempstr,"%s %d", str, num); - DebugAI(tempstr); + UINT16 usAnimState = PickSoldierReadyAnimation( pSoldier, FALSE ); + + if( usAnimState == INVALID_ANIMATION || GetAPsToReadyWeapon( pSoldier, usAnimState ) == 0 ) + { + if( pSoldier->inv[HANDPOS].usItem != NOTHING && Item[pSoldier->inv[HANDPOS].usItem ].usItemClass == IC_GUN ) + return Weapon[ Item[pSoldier->inv[HANDPOS].usItem].ubClassIndex ].ubReadyTime; + } + + return 0; } -void AINameMessage(SOLDIERTYPE * pSoldier,const char* str,INT32 num) + + +UINT32 CountRatingOfTeamMatesDensity( SOLDIERTYPE * pSoldier, INT16 sGridNo, INT32 iDist ) { - STR tempstr; - sprintf((CHAR *) tempstr,"%d %s %d",pSoldier->name , str, num); - DebugAI( tempstr ); + //UINT8 X, Y , ubWhoIsThere; + DOUBLE ddCount; + +// INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown; +// INT16 iGridNo; + DOUBLE ddDistance; + UINT32 uiLoop; + SOLDIERTYPE *pFriend; + DOUBLE ddMaxDistance = iDist; + + ddCount = 0; + + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) + { + pFriend = MercSlots[ uiLoop ]; + + // it's me! Next merc + if( pFriend == pSoldier ) + continue; + + + // if this merc is inactive, at base, on assignment, dead, unconscious + if (!pFriend || (pFriend->bLife < OKLIFE)) + { + continue; // next merc + } + + // if this man is neutral / on the same side + if ( pSoldier->bSide != pFriend->bSide ) + { + continue; // next merc + } + + + ddDistance = (INT32)(GetRangeInCellCoordsFromGridNoDiff( sGridNo, pFriend->sGridNo ) / 10); + + if( ddDistance > ddMaxDistance) + ddDistance = ddMaxDistance; + +// if( ddDistance ) +// ddDistance = iDist + 1 - ddDistance; + + ddCount += max( 0, ddMaxDistance - ddDistance ); + } + + return( ddCount ); +} + +UINT32 CountRatingOfOpponentsDensity( SOLDIERTYPE * pSoldier, INT16 sGridNo, INT32 iDist ) +{ + //UINT8 X, Y , ubWhoIsThere; + DOUBLE ddCount; + +// INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown; +// INT16 iGridNo; + DOUBLE ddDistance; + UINT32 uiLoop; + SOLDIERTYPE *pOpponent; + DOUBLE ddMaxDistance = iDist; + + ddCount = 0; + + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) + { + pOpponent = MercSlots[ uiLoop ]; + + // it's me! Next merc + if( pOpponent == pSoldier ) + continue; + + + // if this merc is inactive, at base, on assignment, dead, unconscious + if (!pOpponent || (pOpponent->bLife < OKLIFE)) + { + continue; // next merc + } + + // if this man is neutral / on the same side + if ( CONSIDERED_NEUTRAL( pSoldier, pOpponent ) || (pSoldier->bSide == pOpponent->bSide)) + { + continue; // next merc + } + + + ddDistance = (INT32)(GetRangeInCellCoordsFromGridNoDiff( sGridNo, pOpponent->sGridNo ) / 10); + + if( ddDistance > ddMaxDistance) + ddDistance = ddMaxDistance; + +// if( ddDistance ) +// ddDistance = iDist + 1 - ddDistance; + + ddCount += max( 0, ddMaxDistance - ddDistance ); + } + + return( ddCount ); } -#endif \ No newline at end of file diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index be6c2238..742ddeea 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -16,6 +16,8 @@ #include "Isometric Utils.h" #endif +#define ATTACK_VALUE_MULTIPLIER_FOR_SMOKE_GRENADES 0.5 + extern INT16 DirIncrementer[8]; // diff --git a/TacticalAI/CreatureDecideAction.cpp b/TacticalAI/CreatureDecideAction.cpp index 7d88f5da..0bae3fcd 100644 --- a/TacticalAI/CreatureDecideAction.cpp +++ b/TacticalAI/CreatureDecideAction.cpp @@ -766,7 +766,7 @@ INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) #ifdef DEBUGDECISIONS STR16 tempstr; sprintf((CHAR *)tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d", - pSoldier->name,pSoldier->ubCaller,pSoldier->usActionData); + pSoldier->name,sClosestFriend,pSoldier->usActionData); AIPopMessage(tempstr); #endif return(AI_ACTION_SEEK_FRIEND); @@ -1350,7 +1350,7 @@ INT8 CreatureDecideActionBlack( SOLDIERTYPE * pSoldier ) sprintf((CHAR *) tempstr, "%d(%s) %s %d(%s) at gridno %d (%d APs aim)\n", pSoldier->ubID,pSoldier->name, (ubBestAttackAction == AI_ACTION_FIRE_GUN)?"SHOOTS":((ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE)?"TOSSES AT":"STABS"), - BestAttack.ubOpponent, "Some Guy", + BestAttack.ubOpponent,ExtMen[BestAttack.ubOpponent].name, BestAttack.sTarget,BestAttack.ubAimTime) ; DebugAI( tempstr ); #endif diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 1cf32066..ec16eb7f 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -23,9 +23,89 @@ // #include "Air Raid.h" #endif +INT32 gpTurnMem[ MAX_NUMBER_OF_TM ]; extern BOOLEAN InternalIsValidStance( SOLDIERTYPE *pSoldier, INT8 bDirection, INT8 bNewStance ); extern BOOLEAN gfHiddenInterrupt; extern BOOLEAN gfUseAlternateQueenPosition; +#define BONUS_FOR_THROW_CHANCE_TO_HIT ((INT32)50) + +#define ADD_MAN_ON_ROOF_ON_EACH_N_SQUARE 1//18 +#define MAX_DISTANCE_TO_ASK_NOISE 10 +#define CHANCE_TO_STAY_ON_ROOF 95 +#define MIN_RANGE_TO_ENEMY_TO_DO_SOMETHING 5 + +#define MIN_OVERALL_ROOF_BONUS 7 +#define MIN_PENALTY_TO_ATTACK_ENEMY_ROOF 9 +#define MAX_HIT_CHANCE_TO_ATTACK_ENEMY_ROOF 60 + +#define COST_OF_READY_AP 2 + +#define EXTRA_CHANCE_OF_BURST_IF_CAN_FIRE_WITH_SAME_AIM_TIME 50 + +static INT8 DecideActionOnClimbSpot( SOLDIERTYPE* pSoldier ) +{ +// INT8 bRightDir = FindDirectionForClimbing( pSoldier->sGridNo ); +// INT8 bAPForLook = 0; +// INT8 bAPForStandUp = 0; + +// if( pSoldier->bDirection != bRightDir ) bAPForLook = 1; +// if( PTR_CROUCHED ) bAPForStandUp = 2; +// if( PTR_PRONE ) bAPForStandUp = 4; + + pSoldier->bAction = AI_ACTION_CLIMB_ROOF; +// pSoldier->bActionPoints -= bAPForStandUp + bAPForLook; + if( IsActionAffordable(pSoldier) ) + { + // pSoldier->bActionPoints += bAPForStandUp + bAPForLook; + +/// gpTurnMem[ TM_TRIED_TO_CLIMB ] = TRUE; + +// if( bAPForStandUp == 4 ) +// pSoldier->usActionData = ANIM_CROUCH; +// if( bAPForStandUp == 2 ) +// pSoldier->usActionData = ANIM_STAND; + +// if( bAPForStandUp ) +// return AI_ACTION_CHANGE_STANCE; + +/* if( bAPForLook ) + { + pSoldier->usActionData = bRightDir; + return(AI_ACTION_CHANGE_FACING); + }*/ + + return AI_ACTION_CLIMB_ROOF; + }//else +// pSoldier->bActionPoints += bAPForStandUp + bAPForLook; + +/* if( pSoldier->bActionPoints > 2 && PTR_STANDING && pSoldier->bDirection != bRightDir ) + { + pSoldier->usActionData = bRightDir; + return(AI_ACTION_CHANGE_FACING); + }else + if( pSoldier->bActionPoints > 6 && PTR_CROUCHED && pSoldier->bDirection != bRightDir ) + { + pSoldier->usActionData = bRightDir; + return(AI_ACTION_CHANGE_FACING); + }*/ + + if( pSoldier->bActionPoints > 1 && PTR_STANDING ) + { + pSoldier->usActionData = ANIM_CROUCH; + return( AI_ACTION_CHANGE_STANCE ); + } + + if( pSoldier->bLevel && pSoldier->bActionPoints > 1 && PTR_CROUCHED ) + { + pSoldier->usActionData = ANIM_PRONE; + return( AI_ACTION_CHANGE_STANCE ); + } + + return AI_ACTION_NONE; +} + + + // global status time counters to determine what takes the most time @@ -36,6 +116,11 @@ UINT32 guiRedSeekTimeTotal = 0, guiRedHelpTimeTotal = 0, guiRedHideTimeTotal = 0 UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0; #endif +// maximum strength bonus or penalty for chance tofire burst +#define BURST_MAX_STRENGTH_BONUS 25 +#define BURST_BONUS_PER_AP 3 +#define OUT_OF_RANGE_GET_CLOSE_BASE_CHANCE 50 + #define CENTER_OF_RING 11237 #define MAX_FLANKS_RED 15 @@ -46,6 +131,10 @@ UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0; #define MIN_FLANK_DIST_RED 10 * STRAIGHT_RATIO #define MAX_FLANK_DIST_RED 40 * STRAIGHT_RATIO +extern UINT8 CountFriendWithoutATarger( SOLDIERTYPE * pSoldier ); + +extern UINT16 PickSoldierReadyAnimation( SOLDIERTYPE *pSoldier, BOOLEAN fEndReady ); + void DoneScheduleAction( SOLDIERTYPE * pSoldier ) { @@ -675,6 +764,7 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); BOOLEAN fCivilianOrMilitia = PTR_CIV_OR_MILITIA; + if( pSoldier->sNoiseGridno == NOWHERE )AskForNoise( pSoldier, &pSoldier->sNoiseGridno, &pSoldier->ubNoiseVolume, &pSoldier->bNoiseLevel, MAX_DISTANCE_TO_ASK_NOISE); gubNPCPathCount = 0; @@ -1227,6 +1317,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) STR16 tempstr; #endif + if( pSoldier->sNoiseGridno == NOWHERE )AskForNoise( pSoldier, &pSoldier->sNoiseGridno, &pSoldier->ubNoiseVolume, &pSoldier->bNoiseLevel, MAX_DISTANCE_TO_ASK_NOISE); if (fCivilian) { @@ -1290,9 +1381,11 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) { // set base chance according to orders if ((pSoldier->bOrders == STATIONARY) || (pSoldier->bOrders == ONGUARD)) - iChance = 50; + iChance = 60; else // all other orders - iChance = 25; + iChance = 35; + if(pSoldier->bOrders == SEEKENEMY) + iChance = -15; if (pSoldier->bAttitude == DEFENSIVE) iChance += 15; @@ -1322,7 +1415,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) (gTacticalStatus.Team[pSoldier->bTeam].bMenInSector > 1) ) { // base chance depends on how much new info we have to radio to the others - iChance = 5 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 5 * for YELLOW alert + iChance = 5 * 5 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 5 * for YELLOW alert // if I actually know something they don't and I ain't swimming (deep water) if (iChance && !DeepWater( pSoldier->sGridNo )) @@ -1344,7 +1437,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) case RNDPTPATROL: case POINTPATROL: break; case FARPATROL: iChance += -10; break; - case SEEKENEMY: iChance += -20; break; + case SEEKENEMY: iChance += 40; break; } // modify base chance according to attitude @@ -1717,7 +1810,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) if (!SkipCoverCheck ) // && gfTurnBasedAI) // only do in turnbased { // remember that noise value is negative, and closer to 0 => more important! - iChance = 25; + iChance = 55; iSneaky = 30; // set base chance according to orders @@ -1774,7 +1867,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) //////////////////////////////////////////////////////////////////////////// // SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong //////////////////////////////////////////////////////////////////////////// - if ((INT16)PreRandom(100) < 50) + if ((INT16)PreRandom(100) < 1) { #ifdef RECORDNET fprintf(NetDebugFile,"\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n",pSoldier->ubID); @@ -1826,6 +1919,7 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { +INT32 iCoverPercentBetter; INT8 bActionReturned; INT32 iDummy; INT16 iChance,sClosestOpponent,sClosestFriend; @@ -1835,6 +1929,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) INT8 bSeekPts = 0, bHelpPts = 0, bHidePts = 0, bWatchPts = 0; INT8 bHighestWatchLoc; ATTACKTYPE BestThrow, BestShot; + INT16 sBestCover = NOWHERE; + #ifdef AI_TIMING_TEST UINT32 uiStartTime, uiEndTime; #endif @@ -1846,6 +1942,23 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) (pSoldier->bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); + double ddRatingDiff = CountRatingOfOpponentsDensity( pSoldier, pSoldier->sGridNo, MAX_DIST )==0?CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ):(CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ) / CountRatingOfOpponentsDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ) ); + + if( !ddRatingDiff )ddRatingDiff = 0.000000001; + + if( pSoldier->sNoiseGridno == NOWHERE )AskForNoise( pSoldier, &pSoldier->sNoiseGridno, &pSoldier->ubNoiseVolume, &pSoldier->bNoiseLevel, MAX_DISTANCE_TO_ASK_NOISE); + + if( !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition && (pSoldier->bActionPoints >= AP_RADIO) && !fCivilian ) + { + return(AI_ACTION_RED_ALERT); + } + + if( gfTurnBasedAI ) + if( gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition ) + { + RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam); + } + // if we have absolutely no action points, we can't do a thing under RED! if (!pSoldier->bActionPoints) { @@ -1995,11 +2108,37 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } } + if (ubCanMove && RangeToClosestOpponent( pSoldier ) > MIN_RANGE_TO_ENEMY_TO_DO_SOMETHING && + !CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, 10 ) && + ddRatingDiff < 1 ) +{ + //////////////////////////////////////////////////////////////////////// + // RUN AWAY TO A FRIEND FARTHEST FROM KNOWN THREATS + //////////////////////////////////////////////////////////////////////// + + SOLDIERTYPE* pFriend = FindFriendFarthestFromEnemies( pSoldier ); + + if( pFriend ) + { + pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,pFriend->sGridNo,AI_ACTION_SEEK_OPPONENT); + if( pSoldier->usActionData != NOWHERE ) + { + if( CountRatingOfOpponentsDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ) >= CountRatingOfOpponentsDensity( pSoldier, pSoldier->usActionData, MAX_DIST ) ) + return(AI_ACTION_RUN_AWAY); + } + } +} + + //////////////////////////////////////////////////////////////////////////// // WHEN IN THE LIGHT, GET OUT OF THERE! //////////////////////////////////////////////////////////////////////////// - if ( ubCanMove && InLightAtNight( pSoldier->sGridNo, pSoldier->bLevel ) && pSoldier->bOrders != STATIONARY ) + if ( ubCanMove && InLightAtNight( pSoldier->sGridNo, pSoldier->bLevel ) && pSoldier->bOrders != STATIONARY + && pSoldier->bActionPoints <= pSoldier->bInitialActionPoints / 2)// && pSoldier->bActionInProgress != AI_ACTION_MOVE_TO_CLIMB ) { + if( gfTurnBasedAI ) + pSoldier->usActionData = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iCoverPercentBetter); + else pSoldier->usActionData = FindNearbyDarkerSpot( pSoldier ); if ( pSoldier->usActionData != NOWHERE ) { @@ -2367,7 +2506,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: calculate morale"); return(AI_ACTION_RUN_AWAY); } } - +/* DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); //////////////////////////////////////////////////////////////////////////// @@ -2449,7 +2588,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); } } } - +*/ if ( !TANK( pSoldier ) ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai"); @@ -2570,8 +2709,8 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); // modify RED movement tendencies according to morale switch (pSoldier->bAIMorale) { - case MORALE_HOPELESS: bSeekPts = -99; bHelpPts = -99; bHidePts = +2; bWatchPts = -99; break; - case MORALE_WORRIED: bSeekPts += -2; bHelpPts += 0; bHidePts += +2; bWatchPts += 1; break; + case MORALE_HOPELESS: bSeekPts = -99; bHelpPts = -99; bHidePts = +1; bWatchPts = -99; break; + case MORALE_WORRIED: bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += 1; break; case MORALE_NORMAL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break; case MORALE_CONFIDENT: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += 0; break; case MORALE_FEARLESS: bSeekPts += +1; bHelpPts += 0; bHidePts = -1; bWatchPts += 0; break; @@ -2580,14 +2719,14 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); // modify tendencies according to orders switch (pSoldier->bOrders) { - case STATIONARY: bSeekPts += -1; bHelpPts += -1; bHidePts += +1; bWatchPts += +1; break; - case ONGUARD: bSeekPts += -1; bHelpPts += 0; bHidePts += +1; bWatchPts += +1; break; - case CLOSEPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break; - case RNDPTPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break; - case POINTPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break; - case FARPATROL: bSeekPts += 0; bHelpPts += 0; bHidePts += 0; bWatchPts += 0; break; + case STATIONARY: bSeekPts += -1; bHelpPts += +1; bHidePts += +1; bWatchPts += +1; break; + case ONGUARD: bSeekPts += -1; bHelpPts += +1; bHidePts += +1; bWatchPts += +1; break; + case CLOSEPATROL: bSeekPts += 0; bHelpPts += +1; bHidePts += 0; bWatchPts += 0; break; + case RNDPTPATROL: bSeekPts += 0; bHelpPts += +1; bHidePts += 0; bWatchPts += 0; break; + case POINTPATROL: bSeekPts += 0; bHelpPts += +1; bHidePts += 0; bWatchPts += 0; break; + case FARPATROL: bSeekPts += 0; bHelpPts += +1; bHidePts += 0; bWatchPts += 0; break; case ONCALL: bSeekPts += 0; bHelpPts += +1; bHidePts += -1; bWatchPts += 0; break; - case SEEKENEMY: bSeekPts += +1; bHelpPts += 0; bHidePts += -1; bWatchPts += -1; break; + case SEEKENEMY: bSeekPts += +1; bHelpPts += +1; bHidePts += +1; bWatchPts += -1; break; } // modify tendencies according to attitude @@ -2614,6 +2753,9 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); // don't search for cover bHidePts = -99; } + else + bHelpPts = -99; + // while one of the three main RED REACTIONS remains viable while ((bSeekPts > -90) || (bHelpPts > -90) || (bHidePts > -90) ) @@ -2625,7 +2767,17 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); #ifdef AI_TIMING_TESTS uiStartTime = GetJA2Clock(); #endif - + + // get the location of the closest reachable opponent + sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb); + + if( sClosestDisturbance == NOWHERE ) + { + INT32 iNV; + BOOLEAN fR; + sClosestDisturbance = MostImportantNoiseHeard( pSoldier, &iNV, &fClimb, &fR ); + } + #ifdef AI_TIMING_TESTS uiEndTime = GetJA2Clock(); guiRedSeekTimeTotal += (uiEndTime - uiStartTime); @@ -2867,6 +3019,12 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); ////////////////////////////////////////////////////////////////////// pSoldier->usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestFriend,AP_CROUCH, AI_ACTION_SEEK_OPPONENT,0); + if ( gfTurnBasedAI && pSoldier->usActionData != NOWHERE ) + { + pSoldier->usActionData = FindBestCoverNearTheGridNo( pSoldier, pSoldier->usActionData, 5 ); + pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,pSoldier->usActionData,AI_ACTION_SEEK_OPPONENT); + } + if (pSoldier->usActionData != NOWHERE) { #ifdef DEBUGDECISIONS @@ -2972,9 +3130,20 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't hide"); } } + ///////////////////////////////////////////////////////////////////////////// + // GET TO BETTER COVER + ///////////////////////////////////////////////////////////////////////////// + + if ( ubCanMove && ( ClosestReachableDisturbance(pSoldier, TRUE, &fClimb) == NOWHERE) ) + { + sBestCover = FindBestNearbyCover(pSoldier,pSoldier->bAIMorale,&iCoverPercentBetter); + if( sBestCover != NOWHERE) + { + pSoldier->usActionData = sBestCover; + return(AI_ACTION_TAKE_COVER); + } } - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: nothing to do!"); //////////////////////////////////////////////////////////////////////////// // NOTHING USEFUL POSSIBLE! IF NPC IS CURRENTLY UNDER FIRE, TRY TO RUN AWAY @@ -3273,12 +3442,13 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); // DO NOTHING: Not enough points left to move, so save them for next turn //////////////////////////////////////////////////////////////////////////// -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"- DOES NOTHING (RED)",1000); -#endif + } + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"- DOES NOTHING (RED)",1000); + #endif - pSoldier->usActionData = NOWHERE; - return(AI_ACTION_NONE); + pSoldier->usActionData = NOWHERE; + return(AI_ACTION_NONE); } INT8 DecideActionBlack(SOLDIERTYPE *pSoldier) @@ -3305,14 +3475,33 @@ INT8 DecideActionBlack(SOLDIERTYPE *pSoldier) UINT8 ubBurstAPs; UINT8 ubOpponentDir; INT16 sCheckGridNo; + INT16 sCGridNo; + INT8 bLevel; +// INT8 bIndex; + INT8 bRealActionPoints; + + double ddRatingDiff = CountRatingOfOpponentsDensity( pSoldier, pSoldier->sGridNo, MAX_DIST )==0?CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ):(CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ) / CountRatingOfOpponentsDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ) ); BOOLEAN fAllowCoverCheck = FALSE; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack"); + if( !ddRatingDiff )ddRatingDiff = 0.000000001; + if( gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition ) + { + RadioSightings(pSoldier,EVERYBODY,pSoldier->bTeam); + } + + + if( !gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition ) + if( (pSoldier->bActionPoints >= AP_RADIO) && !fCivilian ) + { + return(AI_ACTION_RED_ALERT); + } // once we hit status black, reset flanking status //pSoldier->numFlanks = 0; + if( pSoldier->sNoiseGridno == NOWHERE )AskForNoise( pSoldier, &pSoldier->sNoiseGridno, &pSoldier->ubNoiseVolume, &pSoldier->bNoiseLevel, MAX_DISTANCE_TO_ASK_NOISE); // if we have absolutely no action points, we can't do a thing under BLACK! if (!pSoldier->bActionPoints) @@ -3967,6 +4156,153 @@ bCanAttack = FALSE; } + if (ubCanMove && RangeToClosestOpponent( pSoldier ) > MIN_RANGE_TO_ENEMY_TO_DO_SOMETHING && + !CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, 10 ) && + ddRatingDiff < 1 ) +{ + //////////////////////////////////////////////////////////////////////// + // RUN AWAY TO A FRIEND FARTHEST FROM KNOWN THREATS + //////////////////////////////////////////////////////////////////////// + + SOLDIERTYPE* pFriend = FindFriendFarthestFromEnemies( pSoldier ); + + if( pFriend ) + { + pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,pFriend->sGridNo,AI_ACTION_SEEK_OPPONENT); + if( pSoldier->usActionData != NOWHERE ) + { + if( CountRatingOfOpponentsDensity( pSoldier, pSoldier->sGridNo, MAX_DIST ) >= CountRatingOfOpponentsDensity( pSoldier, pSoldier->usActionData, MAX_DIST ) ) + return(AI_ACTION_RUN_AWAY); + } + } +} + + + + + //////////////////////////////////////////////////////////////////////////// + // IF THE TARGET IS OUT OF WEAPON RANGE + //////////////////////////////////////////////////////////////////////////// + + if( gpTurnMem[TM_LAST_DEST_GRID] && gpTurnMem[TM_OUTOFRANGE] ) + { + if( gpTurnMem[TM_LAST_DEST_GRID] != pSoldier->sGridNo && ( !BestAttack.ubPossible || BestAttack.ubChanceToReallyHit < 80 ) ) + { + pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,gpTurnMem[TM_LAST_DEST_GRID],AI_ACTION_SEEK_OPPONENT); + if( pSoldier->usActionData != NOWHERE ) + return AI_ACTION_GET_CLOSER; + } + } + else + if( ubCanMove && ubBestAttackAction == AI_ACTION_FIRE_GUN + && Item[pSoldier->inv[HANDPOS].usItem].usItemClass == IC_GUN + && RangeToClosestOpponent( pSoldier ) > MIN_RANGE_TO_ENEMY_TO_DO_SOMETHING + && pSoldier->bActionPoints > 5 + NeedTimeToReadyGun_IfAlreadyReady( pSoldier ) + && BestAttack.ubChanceToReallyHit < 90 ) +{ + WEAPONTYPE WeapBuf = Weapon[ Item[pSoldier->inv[HANDPOS].usItem].ubClassIndex ]; + + int iDistance = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, BestAttack.sTarget ); + +// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld %ld %ld %ld", pSoldier->ubID, iDistance, BestAttack.ubChanceToReallyHit, ddRatingDiff / 10); + + if( ( iDistance > WeapBuf.usRange && BestAttack.ubChanceToReallyHit < min( 90, 70 * ddRatingDiff ) ) || + ( BestAttack.ubChanceToReallyHit < min( 70, 30 * ddRatingDiff ) ) ) + { + sClosestDisturbance = ClosestKnownOpponent( pSoldier, 0, &bLevel ); + + iChance = OUT_OF_RANGE_GET_CLOSE_BASE_CHANCE * iDistance / WeapBuf.usRange + 100 - BestAttack.ubChanceToReallyHit; + + switch (pSoldier->bAttitude) + { + case DEFENSIVE: iChance += -50; break; + case BRAVESOLO: iChance += -10; break; + case BRAVEAID: iChance += -5; break; + case CUNNINGSOLO: iChance += -40; break; + case CUNNINGAID: iChance += -30; break; + case AGGRESSIVE: iChance += -10; break; + case ATTACKSLAYONLY:iChance += 0; break; + } + + iChance -= NeedTimeToReadyGun_IfAlreadyReady( pSoldier ) * COST_OF_READY_AP; + + + if( sClosestDisturbance != NOWHERE /*&& iChance > Random( 100 )*/ ) + { + // try to move towards him + + bRealActionPoints = pSoldier->bActionPoints; + + if( iDistance <= WeapBuf.usRange ) + pSoldier->bActionPoints = min( 4 + 2 * iDistance/10 * ( (30 - BestAttack.ubChanceToReallyHit) / 30.0 ), bRealActionPoints ); + else + pSoldier->bActionPoints = min( 9 + (iDistance - WeapBuf.usRange) / 10, bRealActionPoints ); + + + pSoldier->usActionData = GoAsFarAsPossibleTowards( pSoldier, sClosestDisturbance, AI_ACTION_SEEK_OPPONENT ); + + pSoldier->bActionPoints = bRealActionPoints; + + if (pSoldier->usActionData != NOWHERE) + { + sCGridNo = FindClimbGridNo( pSoldier, pSoldier->usActionData, -1 ); + + if( sCGridNo == pSoldier->sGridNo ) + return DecideActionOnClimbSpot( pSoldier ); + + if ( sCGridNo != NOWHERE && + ( !pSoldier->bLevel && !InARoom( pSoldier->sGridNo, NULL ) && !gpTurnMem[ TM_TRIED_TO_CLIMBDOWN ]/*|| CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, 5 ) > 5 */) ) + { + gpTurnMem[ TM_OUTOFRANGE ] = TRUE; + gpTurnMem[ TM_TRIED_TO_CLIMBDOWN ] = TRUE; + + pSoldier->usActionData = sCGridNo; + pSoldier->usUIMovementMode = RUNNING; + pSoldier->fUIMovementFast = TRUE; + return( AI_ACTION_MOVE_TO_CLIMB ); + }else + { + + + pSoldier->usActionData = FindBestCoverNearTheGridNo( pSoldier, pSoldier->usActionData, 5 ); + pSoldier->usActionData = GoAsFarAsPossibleTowards(pSoldier,pSoldier->usActionData,AI_ACTION_SEEK_OPPONENT); + +/* if( iDistance <= WeapBuf.usRange ) + switch(pSoldier->usAnimState) + { + case ANIM_PRONE: + pSoldier->usUIMovementMode = CRAWLING; + pSoldier->fUIMovementFast = FALSE; + break; + case ANIM_CROUCH: + pSoldier->usUIMovementMode = SWATTING; + pSoldier->fUIMovementFast = FALSE; + break; + case ANIM_STAND: + pSoldier->usUIMovementMode = RUNNING; + break; + }*/ + gpTurnMem[TM_LAST_DEST_GRID] = pSoldier->usActionData; + gpTurnMem[ TM_OUTOFRANGE ] = TRUE; + return( AI_ACTION_GET_CLOSER ); + } + }else if( ( pSoldier->usActionData = FindClimbGridNo( pSoldier, sClosestDisturbance, -1 ) ) != NOWHERE + && ( !pSoldier->bLevel && !InARoom( pSoldier->sGridNo, NULL ) ) && !gpTurnMem[ TM_TRIED_TO_CLIMBDOWN ] ) + { + if( pSoldier->usActionData == pSoldier->sGridNo ) + return DecideActionOnClimbSpot( pSoldier ); + + gpTurnMem[ TM_OUTOFRANGE ] = TRUE; + gpTurnMem[ TM_TRIED_TO_CLIMBDOWN ] = TRUE; + + pSoldier->usUIMovementMode = RUNNING; + pSoldier->fUIMovementFast = TRUE; + return( AI_ACTION_MOVE_TO_CLIMB ); + } + } + + } +} DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW"); //////////////////////////////////////////////////////////////////////////// // LOOK FOR SOME KIND OF COVER BETTER THAN WHAT WE HAVE NOW @@ -3997,6 +4333,11 @@ bCanAttack = FALSE; { // gotta compare their merits and select the more desirable option iOffense = BestAttack.ubChanceToReallyHit; + if( !gpTurnMem[ TM_LOOKED_FOR_COVER ] && BestAttack.ubChanceToReallyHit < 80 ) + { + iDefense = 100 + iCoverPercentBetter; + gpTurnMem[ TM_LOOKED_FOR_COVER ] = TRUE; + }else iDefense = iCoverPercentBetter; // based on how we feel about the situation, decide whether to attack first @@ -4037,12 +4378,12 @@ bCanAttack = FALSE; switch (pSoldier->bAttitude) { - case DEFENSIVE: iDefense += 30; break; - case BRAVESOLO: iDefense -= 0; break; - case BRAVEAID: iDefense -= 0; break; - case CUNNINGSOLO: iDefense += 20; break; - case CUNNINGAID: iDefense += 20; break; - case AGGRESSIVE: iOffense += 10; break; + case DEFENSIVE: iDefense += 20; break; + case BRAVESOLO: iDefense -= 10; break; + case BRAVEAID: iDefense -= 10; break; + case CUNNINGSOLO: iDefense += 10; break; + case CUNNINGAID: iDefense += 10; break; + case AGGRESSIVE: iOffense += 20; break; case ATTACKSLAYONLY:iOffense += 30; break; } @@ -4363,7 +4704,7 @@ bCanAttack = FALSE; DebugAI( tempstr); #endif - + gpTurnMem[ TM_HASFIRED ] =TRUE; DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE ),pSoldier->bRTPCombat,ubBestAttackAction )); if (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && IsGunBurstCapable( pSoldier, BestAttack.bWeaponIn, FALSE ) && (pSoldier->bTeam != gbPlayerNum || pSoldier->bRTPCombat == RTP_COMBAT_AGGRESSIVE) ) @@ -4468,7 +4809,21 @@ bCanAttack = FALSE; pSoldier->usActionData = sBestCover; return(AI_ACTION_TAKE_COVER); - } + + //////////////////////////////////////////////////////////////////////////// + // PICKUP A NEARBY ITEM THAT'S USEFUL + //////////////////////////////////////////////////////////////////////////// + + if ( ubCanMove && !pSoldier->bNeutral && (gfTurnBasedAI || pSoldier->bTeam == ENEMY_TEAM ) ) + { + + pSoldier->bAction = SearchForItems( pSoldier, SEARCH_GENERAL_ITEMS, pSoldier->inv[HANDPOS].usItem ); + + if (pSoldier->bAction != AI_ACTION_NONE) + { + return( pSoldier->bAction ); + } + } //////////////////////////////////////////////////////////////////////////// @@ -4631,8 +4986,33 @@ bCanAttack = FALSE; } } - // if a militia has absofreaking nothing else to do, maybe they should radio in a report! + //////////////////////////////////////////////////////////////////////////// + // CROUCH OR PRONE, IF THERE'S NOTHING TO DO ELSE + //////////////////////////////////////////////////////////////////////////// +if( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND ) + if( pSoldier->bActionPoints >= GetAPsToChangeStance( pSoldier, ANIM_CROUCH ) + && IsValidStance( pSoldier, ANIM_CROUCH ) ) + { + pSoldier->usActionData = ANIM_CROUCH; + return(AI_ACTION_CHANGE_STANCE); + } + +if( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_CROUCH ) + if( pSoldier->bActionPoints >= GetAPsToChangeStance( pSoldier, ANIM_PRONE ) + && IsValidStance( pSoldier, ANIM_PRONE ) ) + { + sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL); + + if(sClosestOpponent == NOWHERE || GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sClosestOpponent ) / 10 > 10) + { + pSoldier->usActionData = ANIM_PRONE; + return(AI_ACTION_CHANGE_STANCE); + } + } +} +/* +// if a militia has absofreaking nothing else to do, maybe they should radio in a report! //////////////////////////////////////////////////////////////////////////// // RADIO RED ALERT: determine %chance to call others and report contact //////////////////////////////////////////////////////////////////////////// @@ -4642,10 +5022,8 @@ bCanAttack = FALSE; // if there hasn't been an initial RED ALERT yet in this sector if ( !(gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition) || NeedToRadioAboutPanicTrigger() ) - { // since I'm at STATUS RED, I obviously know we're being invaded! - DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to radio contact")); - iChance = gbDiff[DIFF_RADIO_RED_ALERT][ SoldierDifficultyLevel( pSoldier ) ]; - } + // since I'm at STATUS RED, I obviously know we're being invaded! + iChance = gbDiff[DIFF_RADIO_RED_ALERT][ SoldierDifficultyLevel( pSoldier ) ]; else // subsequent radioing (only to update enemy positions, request help) // base chance depends on how much new info we have to radio to the others iChance = 10 * WhatIKnowThatPublicDont(pSoldier,FALSE); // use 10 * for RED alert @@ -4663,7 +5041,7 @@ bCanAttack = FALSE; case RNDPTPATROL: case POINTPATROL: iChance += -5; break; case FARPATROL: iChance += -10; break; - case SEEKENEMY: iChance += -20; break; + case SEEKENEMY: iChance += 40; break; } // modify base chance according to attitude @@ -4712,7 +5090,7 @@ bCanAttack = FALSE; } } } - +*/ //////////////////////////////////////////////////////////////////////////// // LEAVE THE SECTOR //////////////////////////////////////////////////////////////////////////// @@ -4724,15 +5102,13 @@ bCanAttack = FALSE; //////////////////////////////////////////////////////////////////////////// #ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"- DOES NOTHING (BLACK)",1000); +AINameMessage(pSoldier,"- DOES NOTHING (BLACK)",1000); #endif - // by default, if everything else fails, just stand in place and wait - pSoldier->usActionData = NOWHERE; - return(AI_ACTION_NONE); - +// by default, if everything else fails, just stand in place and wait +pSoldier->usActionData = NOWHERE; +return(AI_ACTION_NONE); } - INT8 DecideAction(SOLDIERTYPE *pSoldier) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction"); @@ -4849,14 +5225,14 @@ INT8 DecideAction(SOLDIERTYPE *pSoldier) -#ifdef DEBUGDECISIONS - sprintf((CHAR *) tempstr,"DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->usActionData); - DebugAI((STR) tempstr ); -#endif + #ifdef DEBUGDECISIONS + sprintf((CHAR *) tempstr,"DecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->usActionData); + DebugAI((STR) tempstr ); + #endif -DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction done"); - - return(bAction); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideAction done"); + + return(bAction); } INT8 DecideActionEscort(SOLDIERTYPE *pSoldier) @@ -4864,18 +5240,19 @@ INT8 DecideActionEscort(SOLDIERTYPE *pSoldier) #ifdef DEBUGDECISIONS STR16 tempstr; #endif - // if he has a place to go, and isn't already there... go! - if (pSoldier->usActionData != NOWHERE && (pSoldier->sGridNo != pSoldier->usActionData)) - { -#ifdef DEBUGDECISIONS - sprintf((CHAR *)tempstr,"%s - ESCORTED NPC GOING to grid %d",pSoldier->name,pSoldier->usActionData); - AIPopMessage(tempstr); -#endif + + // if he has a place to go, and isn't already there... go! + if (pSoldier->usActionData != NOWHERE && (pSoldier->sGridNo != pSoldier->usActionData)) + { + #ifdef DEBUGDECISIONS + sprintf((CHAR *)tempstr,"%s - ESCORTED NPC GOING to grid %d",pSoldier->name,pSoldier->usActionData); + AIPopMessage(tempstr); + #endif - return(AI_ACTION_ESCORTED_MOVE); - } - else - return(AI_ACTION_NONE); + return(AI_ACTION_ESCORTED_MOVE); + } + else + return(AI_ACTION_NONE); } diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index 0db54efe..5312d7cf 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -27,6 +27,9 @@ #include "PathAIDebug.h" +#define MINIMUM_DISTANCE_BETWEEN_TEAMMATES 10 +#define MAX_NEAREST_ROOF_SEARCH_RADIUS 15 + #ifdef _DEBUG INT16 gsCoverValue[WORLD_MAX]; INT16 gsBestCover; @@ -523,6 +526,8 @@ UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT16 sGridNo ) return( ubCount ); } +static INT16 gsDesiredGridNo = NOWHERE; + INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter) { DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("FindBestNearbyCover")); @@ -554,6 +559,7 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB UINT8 ubBackgroundLightPercent = 0; UINT8 ubLightPercentDifference; BOOLEAN fNight; + UINT16 usTmDistRatingBuffer; switch( FindObj( pSoldier, GASMASK ) ) { @@ -710,8 +716,14 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB iThreatCertainty = ThreatPercent[*pbPublOL - OLDEST_HEARD_VALUE]; } + sThreatLoc = pOpponent->sGridNo; + iThreatCertainty = 100; + // calculate how far away this threat is (in adjusted pixels) //iThreatRange = AdjPixelsAway(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sThreatLoc),CenterY(sThreatLoc)); + if( gsDesiredGridNo != NOWHERE ) + iThreatRange = GetRangeInCellCoordsFromGridNoDiff( gsDesiredGridNo, sThreatLoc ); + else iThreatRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sThreatLoc ); //NumMessage("Threat Range = ",iThreatRange); @@ -721,7 +733,7 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB #endif // if this opponent is believed to be too far away to really be a threat - if (iThreatRange > iMaxThreatRange) + if (iThreatRange > iMaxThreatRange + Weapon[ Item[pOpponent->inv[HANDPOS].usItem].ubClassIndex ].usRange + GetRangeBonus(&pOpponent->inv[HANDPOS])) { #ifdef DEBUGCOVER // AINameMessage(pOpponent,"is too far away to be a threat",1000); @@ -781,13 +793,51 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB if (Threat[uiLoop].iOrigRange <= MAX_THREAT_RANGE) { // add this opponent's cover value to our current total cover value + if( gsDesiredGridNo == NOWHERE ) iCurrentCoverValue += CalcCoverValue(pSoldier,pSoldier->sGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale); + else + iCurrentCoverValue += CalcCoverValue(pSoldier,gsDesiredGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale); } //sprintf((CHAR *)tempstr,"iCurrentCoverValue after opponent %d is now %d",iLoop,iCurrentCoverValue); //PopMessage(tempstr); } - iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); +// iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); + + + if( gsDesiredGridNo == NOWHERE ) + usTmDistRatingBuffer = CountRatingOfTeamMatesDensity( pSoldier, pSoldier->sGridNo, MINIMUM_DISTANCE_BETWEEN_TEAMMATES ); + else + usTmDistRatingBuffer = CountRatingOfTeamMatesDensity( pSoldier, gsDesiredGridNo, MINIMUM_DISTANCE_BETWEEN_TEAMMATES ); + usTmDistRatingBuffer *= usTmDistRatingBuffer; + +// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld:%ld", sGridNo, usTmDistRatingBuffer ); + + if ( iCurrentCoverValue >= 0 ) + { + iCurrentCoverValue -= (iCurrentCoverValue / 10) * usTmDistRatingBuffer; + } + else + { + // when negative, must add a negative to decrease the total + iCurrentCoverValue += (iCurrentCoverValue / 10) * usTmDistRatingBuffer; + } + + + // It's better to be in a room...I guess... + + if ( gsDesiredGridNo == NOWHERE ? InARoom( pSoldier->sGridNo, NULL ) : InARoom( gsDesiredGridNo, NULL ) ) + { + if ( iCurrentCoverValue >= 0 ) + { + iCurrentCoverValue += 2*iCurrentCoverValue; + } + else + { + iCurrentCoverValue -= 2*iCurrentCoverValue; + } + } + #ifdef DEBUGCOVER // AINumMessage("Search Range = ",iSearchRange); @@ -882,7 +932,11 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB //HandleMyMouseCursor(KEYBOARDALSO); // calculate the next potential gridno + if( gsDesiredGridNo == NOWHERE ) sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); + else + sGridNo = gsDesiredGridNo + sXOffset + (MAXCOL * sYOffset); + if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) ) { continue; @@ -929,6 +983,9 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB } iPathCost = gubAIPathCosts[AI_PATHCOST_RADIUS + sXOffset][AI_PATHCOST_RADIUS + sYOffset]; + + if( iPathCost <= pSoldier->bActionPoints )iPathCost = 0; + /* // water is OK, if the only good hiding place requires us to get wet, OK iPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,WATEROK); @@ -975,14 +1032,25 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // reduce cover for each person adjacent to this gridno who is on our team, // by 10% (so locations next to several people will be very much frowned upon + + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld", iCoverValue ); + +// if( !iCoverValue ) +// iCoverValue -= 100; + + usTmDistRatingBuffer = CountRatingOfTeamMatesDensity( pSoldier, sGridNo, MINIMUM_DISTANCE_BETWEEN_TEAMMATES ); + usTmDistRatingBuffer *= usTmDistRatingBuffer; + +// ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld:%ld", sGridNo, usTmDistRatingBuffer ); + if ( iCoverValue >= 0 ) { - iCoverValue -= (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo ); + iCoverValue -= (iCoverValue / 10) * usTmDistRatingBuffer; } else { // when negative, must add a negative to decrease the total - iCoverValue += (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo ); + iCoverValue += (iCoverValue / 10) * usTmDistRatingBuffer; } if ( fNight && !( InARoom( sGridNo, NULL ) ) ) // ignore in buildings in case placed there @@ -993,15 +1061,31 @@ INT16 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->bLevel ) ] - ubBackgroundLightPercent ); if ( iCoverValue >= 0 ) { - iCoverValue -= (iCoverValue / 100) * ubLightPercentDifference; + iCoverValue -= (iCoverValue / 20) * ubLightPercentDifference; } else { - iCoverValue += (iCoverValue / 100) * ubLightPercentDifference; + iCoverValue += (iCoverValue / 20) * ubLightPercentDifference; } } + // It's better to be in a room...I guess... + + if ( InARoom( sGridNo, NULL ) ) + { + if ( iCoverValue >= 0 ) + { + iCoverValue += 2*iCoverValue; + } + else + { + iCoverValue -= 2*iCoverValue; + } + } + + + #ifdef DEBUGCOVER // if there ARE multiple opponents if (uiThreatCnt > 1) @@ -1616,7 +1700,7 @@ INT16 FindNearbyDarkerSpot( SOLDIERTYPE *pSoldier ) // CJC: here, unfortunately, we must calculate a path so we have an AP cost - sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,NOWATER,0); + sPathCost = LegalNPCDestination(pSoldier,sGridNo,ENSURE_PATH_COST,WATEROK,0); if (!sPathCost) { @@ -2780,3 +2864,246 @@ BOOLEAN CanClimbFromHere (SOLDIERTYPE * pSoldier, BOOLEAN fUp ) return FALSE; } */ +extern BUILDING gBuildings[ MAX_BUILDINGS ]; +extern UINT8 gubNumberOfBuildings; + +BOOLEAN IsItValidClimbSpot( INT16 sGridNo, INT8 bLevel ) +{ + UINT16 usBuildingID; + UINT16 usClimbSpotIndex; + + for( usBuildingID = 1; usBuildingID <= gubNumberOfBuildings ; ++usBuildingID ) + for( usClimbSpotIndex = 0; usClimbSpotIndex < gBuildings[ usBuildingID ].ubNumClimbSpots ; ++usClimbSpotIndex ) + if( gBuildings[ usBuildingID ].sUpClimbSpots[ usClimbSpotIndex ] == sGridNo && !bLevel ) + { + if( WhoIsThere2( gBuildings[ usBuildingID ].sDownClimbSpots[ usClimbSpotIndex ], 1 ) == NOBODY ) + return TRUE; + }else if( gBuildings[ usBuildingID ].sDownClimbSpots[ usClimbSpotIndex ] == sGridNo && bLevel ) + { + if( WhoIsThere2( gBuildings[ usBuildingID ].sUpClimbSpots[ usClimbSpotIndex ], 0 ) == NOBODY ) + return TRUE; + } + return FALSE; +} + +BOOLEAN AreGridsAdjacent( INT16 sFGridNo, INT16 sSGridNo ) +{ + return sFGridNo == sSGridNo - 1 || + sFGridNo == sSGridNo + 1 || + sFGridNo == sSGridNo - MAXCOL || + sFGridNo == sSGridNo + MAXCOL; +} + +INT16 FindClimbGridNo( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 bMaxActionPoints ) +{ + BOOLEAN fClimbingNecessary; + INT16 sCGridNo; + INT16 sActionPointsNeeded = EstimatePathCostToLocation( pSoldier, sGridNo, 1, FALSE, &fClimbingNecessary, &sCGridNo ); + + if( sActionPointsNeeded != 0 && fClimbingNecessary ) + { + if( IsItValidClimbSpot( pSoldier->sGridNo, pSoldier->bLevel ) && AreGridsAdjacent( pSoldier->sGridNo, sCGridNo ) ) return pSoldier->sGridNo; + + if( bMaxActionPoints < 0 || ( bMaxActionPoints >= 0 && sActionPointsNeeded <= bMaxActionPoints ) ) + return sCGridNo; + else + return NOWHERE; + } + + return NOWHERE; +} + +INT16 FindBestCoverNearTheGridNo(SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubSearchRadius ) +{ + INT32 iPercentBetter; +// INT16 sTrueGridNo; + INT16 sResultGridNo; + INT8 bRealWisdom = pSoldier->bWisdom; + +// sTrueGridNo = pSoldier->sGridNo; +// pSoldier->sGridNo = sGridNo; + gsDesiredGridNo = sGridNo; + pSoldier->bWisdom = 8 * ubSearchRadius;// 5 tiles + + sResultGridNo = FindBestNearbyCover( pSoldier, MORALE_NORMAL, &iPercentBetter); + + pSoldier->bWisdom = bRealWisdom; +// pSoldier->sGridNo = sTrueGridNo; + + gsDesiredGridNo = NOWHERE; + + if( sResultGridNo != NOWHERE ) + return sResultGridNo; + else + return sGridNo; + +} + +INT16 FindRoofBetweenGridNos( INT16 sFGridNo, INT16 sSGridNo ) +{ + INT16 sGridNo; + INT16 sCurrX, sCurrY; + INT16 sFGX, sFGY, sSGX, sSGY; + + ConvertGridNoToXY( sFGridNo, &sFGX, &sFGY ); + ConvertGridNoToXY( sSGridNo, &sSGX, &sSGY ); + +// if( pSoldier->bLevel ) +// return NOWHERE; + +// if ( gbWorldSectorZ > 0 ) +// return NOWHERE; + + + if( sFGX == sSGX ) + { + sCurrX = sFGX; + + if( sFGY < sSGY ) + for( sCurrY = sFGY; sCurrY < sSGY ; ++sCurrY ) + { + sGridNo = sCurrX + (MAXCOL * sCurrY); + if( gubBuildingInfo[ sGridNo ] ) + return sGridNo; + }else + if( sFGY > sSGY ) + for( sCurrY = sFGY; sCurrY > sSGY ; --sCurrY ) + { + sGridNo = sCurrX + (MAXCOL * sCurrY); + if( gubBuildingInfo[ sGridNo ] ) + return sGridNo; + } + + }else + if( sFGX < sSGX ) + for( sCurrX = sFGX;sCurrX < sSGX ; ++sCurrX ) + { + sCurrY = sFGY + ( sSGY - sFGY ) * ( (FLOAT)(sCurrX - sFGX) / (FLOAT)(sSGX - sFGX) ); + sGridNo = sCurrX + (MAXCOL * sCurrY); + + if( gubBuildingInfo[ sGridNo ] ) + return sGridNo; + }else + if( sFGX > sSGX ) + for( sCurrX = sFGX;sCurrX > sSGX ; --sCurrX ) + { + sCurrY = sFGY + ( sSGY - sFGY ) * ( (FLOAT)(sCurrX - sFGX) / (FLOAT)(sSGX - sFGX) ); + sGridNo = sCurrX + (MAXCOL * sCurrY); + + if( gubBuildingInfo[ sGridNo ] ) + return sGridNo; + } + + return NOWHERE; +} + +INT8 FindDirectionForClimbing( INT16 sGridNo ) +{ + UINT16 usBuildingID; + UINT16 usClimbSpotIndex; + + for( usBuildingID = 0; usBuildingID < gubNumberOfBuildings ; ++usBuildingID ) + for( usClimbSpotIndex = 0; usClimbSpotIndex < gBuildings[ usBuildingID ].ubNumClimbSpots ; ++usClimbSpotIndex ) + if( gBuildings[ usBuildingID ].sUpClimbSpots[ usClimbSpotIndex ] == sGridNo ) + { + if( WhoIsThere2( gBuildings[ usBuildingID ].sDownClimbSpots[ usClimbSpotIndex ], 1 ) == NOBODY ) + return atan8(CenterX(sGridNo),CenterY(sGridNo),CenterX(gBuildings[ usBuildingID ].sDownClimbSpots[ usClimbSpotIndex ]),CenterY(gBuildings[ usBuildingID ].sDownClimbSpots[ usClimbSpotIndex ])); + }else if( gBuildings[ usBuildingID ].sDownClimbSpots[ usClimbSpotIndex ] == sGridNo ) + { + if( WhoIsThere2( gBuildings[ usBuildingID ].sUpClimbSpots[ usClimbSpotIndex ], 0 ) == NOBODY ) + return atan8(CenterX(sGridNo),CenterY(sGridNo),CenterX(gBuildings[ usBuildingID ].sUpClimbSpots[ usClimbSpotIndex ]),CenterY(gBuildings[ usBuildingID ].sUpClimbSpots[ usClimbSpotIndex ])); + } + return DIRECTION_IRRELEVANT; +} + + +SOLDIERTYPE* FindFriendFarthestFromEnemies( SOLDIERTYPE * pSoldier ) +{ + UINT32 uiLoop; + SOLDIERTYPE *pFriend; + SOLDIERTYPE *pBestFriend = 0; + UINT16 uiBestEnemyDensity = 0xFFFF; + UINT16 uiCurrEnemyDensity; + + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) + { + pFriend = MercSlots[ uiLoop ]; + + // it's me! Next merc + if( pFriend == pSoldier ) + continue; + + // if this merc is inactive, at base, on assignment, dead, unconscious + if (!pFriend || (pFriend->bLife < OKLIFE)) + { + continue; // next merc + } + + // if this man is neutral / on the same side + if ( pSoldier->bSide != pFriend->bSide ) + { + continue; // next merc + } + + uiCurrEnemyDensity = CountRatingOfOpponentsDensity( pFriend, pFriend->sGridNo, MAX_DIST ); + + if( uiCurrEnemyDensity < uiBestEnemyDensity ) + { + uiBestEnemyDensity = uiCurrEnemyDensity; + pBestFriend = pFriend; + } + } + + if( pBestFriend == pSoldier ) + return 0; + + return pBestFriend; +} + +INT8 DetermineNearestRoof( SOLDIERTYPE *pSoldier ) +{ + INT32 iSearchRange; + INT16 sGridNo, sCGridNo, sBestCGridNo = NOWHERE; + INT8 sBestRoof = NO_BUILDING; + INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset; + UINT8 ubBestDist = 255; + + { + iSearchRange = MAX_NEAREST_ROOF_SEARCH_RADIUS; + + // determine maximum horizontal limits + sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL)); + sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1)); + + // determine maximum vertical limits + sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW)); + sMaxDown = min(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1)); + + + for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++) + { + for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++) + { + sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset); + + if( gubBuildingInfo[ sGridNo ] ) + { + sCGridNo = FindClimbGridNo( pSoldier, sGridNo, -1 ); + if( sCGridNo != NOWHERE && GetRangeFromGridNoDiff( pSoldier->sGridNo, sCGridNo ) < ubBestDist ) + { + ubBestDist = GetRangeFromGridNoDiff( pSoldier->sGridNo, sCGridNo ); + sBestCGridNo = sCGridNo; + sBestRoof = gubBuildingInfo[ sGridNo ]; + + } + } + + } + } + + if( sBestRoof ) + return sBestRoof; + } + + return NO_BUILDING; +} diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index a4bb27e4..e8cc0138 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -239,16 +239,6 @@ BOOLEAN ValidCreatureTurn( SOLDIERTYPE * pCreature, INT8 bNewDirection ); BOOLEAN WearGasMaskIfAvailable( SOLDIERTYPE * pSoldier ); INT16 WhatIKnowThatPublicDont(SOLDIERTYPE *pSoldier, UINT8 ubInSightOnly); -#ifdef DEBUGDECISIONS -void AIPopMessage ( STR16 str ); - -void AIPopMessage ( const char* str ); - -void AINumMessage(const char* str, INT32 num); - -void AINameMessage(SOLDIERTYPE * pSoldier,const char* str,INT32 num); -#endif - INT16 FindClosestClimbPoint (SOLDIERTYPE *pSoldier, BOOLEAN fClimbUp ); INT16 FindFlankingSpot(SOLDIERTYPE *pSoldier, INT16 sPos, INT8 bAction ); BOOLEAN CanClimbFromHere (SOLDIERTYPE * pSoldier, BOOLEAN fUp );