From f40e7fb118607f06b09af8b5ce1673a1067f4243 Mon Sep 17 00:00:00 2001 From: Wanne Date: Wed, 4 Jul 2012 10:30:13 +0000 Subject: [PATCH] - Made the "Zombie MOD" optional, so we can decide if we like to build an executable with/without the appearance of zombies o See: builddefines.h: #define ENABLE_ZOMBIES (then you can turn on/off Zombies in the ingame option screen) o Poison system is still in the code and not combined with the option "Zombie MOD" o Gamers reported on the Bears Pit, that an executable compiled WITH the Zombies mod enabled makes the game unplayable slow in tactical screen after a few minutes in turn based/realtime git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5356 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- GameSettings.cpp | 10 +- GameSettings.h | 4 + Laptop/personnel.cpp | 13 + Options Screen.cpp | Bin 129492 -> 129616 bytes Strategic/Auto Resolve.cpp | 4 + Strategic/Meanwhile.cpp | 3 + Strategic/PreBattle Interface.cpp | 3 + Strategic/Strategic Movement.cpp | 3 + Tactical/Civ Quotes.cpp | 2 + Tactical/Interface Enhanced.cpp | 55 +- Tactical/Interface.cpp | 2 + Tactical/Inventory Choosing.cpp | 8 + Tactical/Merc Entering.cpp | 4 + Tactical/Overhead.cpp | 82 +- Tactical/Overhead.h | 12 +- Tactical/Rotting Corpses.cpp | 778 +++--- Tactical/Rotting Corpses.h | 23 +- Tactical/Soldier Ani.cpp | 2 + Tactical/Soldier Control.cpp | 56 +- Tactical/Soldier Control.h | 4 + Tactical/Soldier Create.cpp | 83 +- Tactical/Soldier Create.h | 4 +- Tactical/Tactical Turns.cpp | 3 + Tactical/TeamTurns.cpp | 3 + Tactical/Weapons.cpp | 2 + Tactical/opplist.cpp | 4 + TacticalAI/AIInternals.h | 14 +- TacticalAI/AIMain.cpp | 11 + TacticalAI/AIUtils.cpp | 162 +- TacticalAI/DecideAction.cpp | 3823 +++++++++++++++-------------- TacticalAI/Realtime.cpp | 4 + TileEngine/Explosion Control.cpp | 57 + Utils/_ChineseText.cpp | 6 + Utils/_DutchText.cpp | 6 + Utils/_EnglishText.cpp | 6 + Utils/_FrenchText.cpp | 6 + Utils/_GermanText.cpp | 6 + Utils/_ItalianText.cpp | 6 + Utils/_PolishText.cpp | 6 + Utils/_RussianText.cpp | 6 + Utils/_TaiwaneseText.cpp | 6 + builddefines.h | 14 +- 42 files changed, 2827 insertions(+), 2479 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index 19e40576..45d879ed 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -254,7 +254,9 @@ BOOLEAN LoadGameSettings() gGameSettings.fOptions[TOPTION_AUTO_FAST_FORWARD_MODE] = FALSE; #endif +#ifdef ENABLE_ZOMBIES gGameSettings.fOptions[TOPTION_ZOMBIES] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_ZOMBIES" , FALSE ); +#endif gGameSettings.fOptions[NUM_ALL_GAME_OPTIONS] = iniReader.ReadBoolean("JA2 Game Settings","NUM_ALL_GAME_OPTIONS" , FALSE ); @@ -417,7 +419,10 @@ BOOLEAN SaveGameSettings() settings << "TOPTION_AUTO_FAST_FORWARD_MODE = " << (gGameSettings.fOptions[TOPTION_AUTO_FAST_FORWARD_MODE] ? "TRUE" : "FALSE" ) << endl; #endif +#ifdef ENABLE_ZOMBIES settings << "TOPTION_ZOMBIES = " << (gGameSettings.fOptions[TOPTION_ZOMBIES] ? "TRUE" : "FALSE" ) << endl; +#endif + settings << "TOPTION_CHEAT_MODE_OPTIONS_HEADER = " << (gGameSettings.fOptions[TOPTION_CHEAT_MODE_OPTIONS_HEADER] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_FORCE_BOBBY_RAY_SHIPMENTS = " << (gGameSettings.fOptions[TOPTION_FORCE_BOBBY_RAY_SHIPMENTS] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_CHEAT_MODE_OPTIONS_END = " << (gGameSettings.fOptions[TOPTION_CHEAT_MODE_OPTIONS_END] ? "TRUE" : "FALSE" ) << endl; @@ -549,8 +554,9 @@ void InitGameSettings() gGameSettings.fOptions[ TOPTION_AUTO_FAST_FORWARD_MODE ] = FALSE; #endif +#ifdef ENABLE_ZOMBIES gGameSettings.fOptions[ TOPTION_ZOMBIES ] = FALSE; // Flugente Zombies 1.0 - +#endif // arynn: Cheat/Debug Menu gGameSettings.fOptions[ TOPTION_CHEAT_MODE_OPTIONS_HEADER ] = FALSE; @@ -1357,6 +1363,7 @@ void LoadGameExternalOptions() gGameExternalOptions.iCooldownModificatorLonelyBarrel = iniReader.ReadFloat ("Tactical Weapon Overheating Settings","OVERHEATING_COOLDOWN_MODIFICATOR_LONELYBARREL", 1.15f, 1.0f, 10.0f); gGameExternalOptions.fSetZeroUponNewSector = iniReader.ReadBoolean("Tactical Weapon Overheating Settings","OVERHEATING_SET_ZERO_UPON_NEW_SECTOR",TRUE); +#ifdef ENABLE_ZOMBIES //################# Tactical Zombie Settings ################## gGameExternalOptions.sZombieRiseBehaviour = iniReader.ReadInteger("Tactical Zombie Settings", "ZOMBIE_RISE_BEHAVIOUR", 0, 0, 3); gGameExternalOptions.fZombieSpawnWaves = iniReader.ReadInteger("Tactical Zombie Settings", "ZOMBIE_SPAWN_WAVES", FALSE); @@ -1368,6 +1375,7 @@ void LoadGameExternalOptions() gGameExternalOptions.fZombieOnlyHeadshotsWork = iniReader.ReadBoolean("Tactical Zombie Settings", "ZOMBIE_ONLY_HEADSHOTS_WORK", FALSE); gGameExternalOptions.sZombieDifficultyLevel = iniReader.ReadInteger("Tactical Zombie Settings", "ZOMBIE_DIFFICULTY_LEVEL", 2, 1, 4); gGameExternalOptions.fZombieRiseWithArmour = iniReader.ReadBoolean("Tactical Zombie Settings", "ZOMBIE_RISE_WITH_ARMOUR", TRUE); +#endif //################# Tactical Poison Settings ################## gGameExternalOptions.ubPoisonBaseMedicalSkillToCure = iniReader.ReadInteger("Tactical Poison Settings", "POISON_BASE_MEDICAL_SKILL_TO_CURE", 50, 1, 100); diff --git a/GameSettings.h b/GameSettings.h index 2dbc12b3..efea7e08 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -95,7 +95,9 @@ enum TOPTION_AUTO_FAST_FORWARD_MODE, // automatically fast forward through AI turns #endif +#ifdef ENABLE_ZOMBIES TOPTION_ZOMBIES, // Flugente Zombies 1.0: allow zombies +#endif // arynn: Debug/Cheat TOPTION_CHEAT_MODE_OPTIONS_HEADER, @@ -369,6 +371,7 @@ typedef struct //Video settings BOOLEAN gfVSync; +#ifdef ENABLE_ZOMBIES // Flugente: zombie settings INT8 sZombieRiseBehaviour; BOOLEAN fZombieSpawnWaves; @@ -380,6 +383,7 @@ typedef struct BOOLEAN fZombieOnlyHeadshotsWork; INT8 sZombieDifficultyLevel; BOOLEAN fZombieRiseWithArmour; +#endif // Flugente: poison settings INT32 ubPoisonBaseMedicalSkillToCure; diff --git a/Laptop/personnel.cpp b/Laptop/personnel.cpp index 4b18ce7a..7df73381 100644 --- a/Laptop/personnel.cpp +++ b/Laptop/personnel.cpp @@ -4959,7 +4959,12 @@ void DisplayDepartedCharStats(INT32 iId, INT32 iSlot, INT32 iState) case 14: // kills mprintf((INT16)(pPersonnelScreenPoints[20].x+(iSlot*TEXT_BOX_WIDTH)),(pPersonnelScreenPoints[20].y - 12),pPersonnelScreenStrings[PRSNL_TXT_KILLS]); + +#ifdef ENABLE_ZOMBIES swprintf(sString, L"%d",(gMercProfiles[iId].records.usKillsElites + gMercProfiles[iId].records.usKillsRegulars + gMercProfiles[iId].records.usKillsAdmins + gMercProfiles[iId].records.usKillsHostiles + gMercProfiles[iId].records.usKillsCreatures + gMercProfiles[iId].records.usKillsZombies + gMercProfiles[iId].records.usKillsTanks + gMercProfiles[iId].records.usKillsOthers)); +#else + swprintf(sString, L"%d",(gMercProfiles[iId].records.usKillsElites + gMercProfiles[iId].records.usKillsRegulars + gMercProfiles[iId].records.usKillsAdmins + gMercProfiles[iId].records.usKillsHostiles + gMercProfiles[iId].records.usKillsCreatures + gMercProfiles[iId].records.usKillsTanks + gMercProfiles[iId].records.usKillsOthers)); +#endif FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[20].x+(iSlot*TEXT_BOX_WIDTH)),0,TEXT_BOX_WIDTH-20,0,sString, PERS_FONT, &sX, &sY); mprintf(sX,(pPersonnelScreenPoints[20].y - 12),sString); @@ -6828,7 +6833,12 @@ DEF:3/19/99: case 14: // kills mprintf((INT16)(pPersonnelScreenPoints[20].x+(iSlot*TEXT_BOX_WIDTH)),(pPersonnelScreenPoints[20].y - 12),pPersonnelScreenStrings[PRSNL_TXT_KILLS]); + +#ifdef ENABLE_ZOMBIES swprintf(sString, L"%d",(gMercProfiles[Menptr[iId].ubProfile].records.usKillsElites + gMercProfiles[Menptr[iId].ubProfile].records.usKillsRegulars + gMercProfiles[Menptr[iId].ubProfile].records.usKillsAdmins + gMercProfiles[Menptr[iId].ubProfile].records.usKillsHostiles + gMercProfiles[Menptr[iId].ubProfile].records.usKillsCreatures + gMercProfiles[Menptr[iId].ubProfile].records.usKillsZombies + gMercProfiles[Menptr[iId].ubProfile].records.usKillsTanks + gMercProfiles[Menptr[iId].ubProfile].records.usKillsOthers)); +#else + swprintf(sString, L"%d",(gMercProfiles[Menptr[iId].ubProfile].records.usKillsElites + gMercProfiles[Menptr[iId].ubProfile].records.usKillsRegulars + gMercProfiles[Menptr[iId].ubProfile].records.usKillsAdmins + gMercProfiles[Menptr[iId].ubProfile].records.usKillsHostiles + gMercProfiles[Menptr[iId].ubProfile].records.usKillsCreatures + gMercProfiles[Menptr[iId].ubProfile].records.usKillsTanks + gMercProfiles[Menptr[iId].ubProfile].records.usKillsOthers)); +#endif FindFontRightCoordinates((INT16)(pPersonnelScreenPoints[20].x+(iSlot*TEXT_BOX_WIDTH)),0,TEXT_BOX_WIDTH-20,0,sString, PERS_FONT, &sX, &sY); mprintf(sX,(pPersonnelScreenPoints[20].y - 12),sString); @@ -8239,6 +8249,8 @@ void AssignPersonnelKillsHelpText( INT32 IMercId ) swprintf(atStr, pPersonnelRecordsHelpTexts[ 6 ], gMercProfiles[Menptr[IMercId].ubProfile].records.usKillsOthers ); wcscat( apStr, atStr ); } + +#ifdef ENABLE_ZOMBIES if (gGameSettings.fOptions[TOPTION_ZOMBIES] ) { if (gMercProfiles[Menptr[IMercId].ubProfile].records.usKillsZombies > 0 || fShowRecordsIfZero) @@ -8247,6 +8259,7 @@ void AssignPersonnelKillsHelpText( INT32 IMercId ) wcscat( apStr, atStr ); } } +#endif // Set region help text SetRegionFastHelpText( &(gSkillTraitHelpTextRegion[7]), apStr ); diff --git a/Options Screen.cpp b/Options Screen.cpp index 31d598319967129a31451fd95902c39d254acd70..c25e921ad84d743e0fbe1cea1bd3b12282abea30 100644 GIT binary patch delta 189 zcmccenf<~S_J%EtN>enI88R8t7*ZHgfusV1D}x_{BZCuz50Dkl5XIon;0xq=0(rsH z87DKUPoK7kQG{O^s5%d*2c(miforiran#`y)y=V%f$n*?5MxNh$Ts=iGPdmo QQyH6hiBNZ7G2?_V0Ea6hQ~&?~ diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index 646c585c..bc0bff48 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -4598,9 +4598,11 @@ void AttackTarget( SOLDIERCELL *pAttacker, SOLDIERCELL *pTarget ) case SOLDIER_CLASS_CREATURE : gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsCreatures++; break; +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE : gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsZombies++; break; +#endif default : if ( CREATURE_OR_BLOODCAT( pTarget->pSoldier ) ) gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsCreatures++; @@ -4789,9 +4791,11 @@ void TargetHitCallback( SOLDIERCELL *pTarget, INT32 index ) case SOLDIER_CLASS_CREATURE : gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsCreatures++; break; +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE : gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsZombies++; break; +#endif default : if ( CREATURE_OR_BLOODCAT( pTarget->pSoldier ) ) gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsCreatures++; diff --git a/Strategic/Meanwhile.cpp b/Strategic/Meanwhile.cpp index 18f132ac..acfe067f 100644 --- a/Strategic/Meanwhile.cpp +++ b/Strategic/Meanwhile.cpp @@ -820,7 +820,10 @@ void EndMeanwhile( ) { // We leave this sector open for our POWs to escape! // Set music mode to enemy present! +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); // ATE: Restore people to saved positions... diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index 1734e414..a2e01116 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -867,7 +867,10 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) //Disable the options button when the auto resolve screen comes up EnableDisAbleMapScreenOptionsButton( FALSE ); +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); #ifdef JA2UB diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 9eef45dc..8f06ecc3 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -959,7 +959,10 @@ void PrepareForPreBattleInterface( GROUP *pPlayerDialogGroup, GROUP *pInitiating } //Set music +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); if( gfTacticalTraversal && pInitiatingBattleGroup == gpTacticalTraversalGroup || diff --git a/Tactical/Civ Quotes.cpp b/Tactical/Civ Quotes.cpp index fc1306f3..ef5fc64a 100644 --- a/Tactical/Civ Quotes.cpp +++ b/Tactical/Civ Quotes.cpp @@ -1035,9 +1035,11 @@ void StartEnemyTaunt( SOLDIERTYPE *pCiv, INT8 iTauntType ) VIDEO_OVERLAY_DESC VideoOverlayDesc; CHAR16 gzTauntQuote[ 320 ]; +#ifdef ENABLE_ZOMBIES // Flugente: zombies don't talk if ( pCiv->IsZombie() ) return; +#endif // if we have a different quote on, return, this one is not important if ( gCivQuoteData.bActive ) diff --git a/Tactical/Interface Enhanced.cpp b/Tactical/Interface Enhanced.cpp index bbcac6e8..c4afa8c2 100644 --- a/Tactical/Interface Enhanced.cpp +++ b/Tactical/Interface Enhanced.cpp @@ -1926,13 +1926,8 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } } - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies - { - //////////////////// POISON PERCENTAGE - { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); - } - } + //////////////////// POISON PERCENTAGE + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); } } @@ -3864,10 +3859,11 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } } } - + +//#ifdef ENABLE_ZOMBIES // Flugente Zombies - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) - { + //if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) + //{ if(!cnt) cnt += 2; if (cnt >= sFirstLine && cnt < sLastLine) @@ -3908,7 +3904,8 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } cnt++; } - } + //} +//#endif gubDescBoxTotalAdvLines = (UINT8)cnt; } @@ -4304,13 +4301,15 @@ void DrawAmmoStats( OBJECTTYPE * gpItemDescObject ) BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoAmmoIcon, 16, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies - { +//#ifdef ENABLE_ZOMBIES + //if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies + //{ //////////////// POISON PERCENTAGE - { + //{ BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWH40KIcon, 6, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - } - } + //} + //} +//#endif DrawSecondaryStats( gpItemDescObject ); } @@ -5198,9 +5197,10 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) } } +//#ifdef ENABLE_ZOMBIES // Flugente Zombies - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) - { +// if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) +// { if(!cnt) cnt += 2; ++cnt; // for the new index-line @@ -5215,7 +5215,8 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) } cnt++; } - } + //} +//#endif } void DrawMiscStats( OBJECTTYPE * gpItemDescObject ) @@ -7597,8 +7598,9 @@ void DrawAmmoValues( OBJECTTYPE * gpItemDescObject, int shotsLeft ) mprintf( usX, usY, pStr ); } - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies - { +//#ifdef ENABLE_ZOMBIES +// if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies +// { ///////////////////// POISON PERCENTAGE // Set line to draw into ubNumLine = 4; @@ -7648,7 +7650,8 @@ void DrawAmmoValues( OBJECTTYPE * gpItemDescObject, int shotsLeft ) swprintf( pStr, L"%d", finalvalue ); FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); mprintf( usX, usY, pStr ); - } +// } +//#endif } else if (gubDescBoxPage == 2) { @@ -11170,8 +11173,9 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } // Flugente Zombies - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) - { +//#ifdef USE_ZOMBIES +// if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) + //{ ///////////////////////////////////////////////////// INDEXES if(!cnt) cnt += 2; @@ -11252,7 +11256,8 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } cnt++; } - } + //} +//#endif } void DrawMiscValues( OBJECTTYPE * gpItemDescObject ) diff --git a/Tactical/Interface.cpp b/Tactical/Interface.cpp index cf641ddb..11d7292a 100644 --- a/Tactical/Interface.cpp +++ b/Tactical/Interface.cpp @@ -2211,6 +2211,7 @@ void DrawSelectedUIAboveGuy( UINT16 usSoldierID ) } } +#ifdef ENABLE_ZOMBIES if ( pSoldier->IsZombie() ) { swprintf(NameStr, pSoldier->name); @@ -2225,6 +2226,7 @@ void DrawSelectedUIAboveGuy( UINT16 usSoldierID ) gprintfdirty( sX, sY, NameStr ); mprintf( sX, sY, NameStr ); } +#endif //------------ } } diff --git a/Tactical/Inventory Choosing.cpp b/Tactical/Inventory Choosing.cpp index 3a8ac7e4..87c7e429 100644 --- a/Tactical/Inventory Choosing.cpp +++ b/Tactical/Inventory Choosing.cpp @@ -847,9 +847,11 @@ void GenerateRandomEquipment( SOLDIERCREATE_STRUCT *pp, INT8 bSoldierClass, INT8 } break; +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE: // zombies get no items at all... break; +#endif } for ( i = 0; i < pp->Inv.size(); i++ ) @@ -1056,8 +1058,11 @@ void ChooseWeaponForSoldierCreateStruct( SOLDIERCREATE_STRUCT *pp, INT8 bWeaponC bStatus += (INT8)(HighestPlayerProgressPercentage() / 10); bStatus = (INT8)min( 100, bStatus ); break; + +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE: bStatus = (INT8)(1); // if a zombie gets anything, it'll be broken +#endif default: bStatus = (INT8)(50 + Random( 51 ) ); break; @@ -2111,8 +2116,11 @@ void ChooseFaceGearForSoldierCreateStruct( SOLDIERCREATE_STRUCT *pp ) break; case SOLDIER_CLASS_ADMINISTRATOR: case SOLDIER_CLASS_GREEN_MILITIA: +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE: +#endif break; + } } diff --git a/Tactical/Merc Entering.cpp b/Tactical/Merc Entering.cpp index ef8bcaf5..5f880c6d 100644 --- a/Tactical/Merc Entering.cpp +++ b/Tactical/Merc Entering.cpp @@ -907,8 +907,12 @@ void HandleFirstHeliDropOfGame( ) { // Say quote..... SayQuoteFromAnyBodyInSector( QUOTE_ENEMY_PRESENCE ); + // Start music +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); } else diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 69f4857d..9a657c46 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -6186,7 +6186,10 @@ void ExitCombatMode( ) // unused //gfForceMusicToTense = TRUE; +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); BetweenTurnsVisibilityAdjustments(); @@ -6245,7 +6248,11 @@ void SetEnemyPresence( ) #endif { + +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("SetEnemyPresence: warnings = false")); sniperwarning = FALSE; @@ -6583,7 +6590,10 @@ BOOLEAN CheckForEndOfCombatMode( BOOLEAN fIncrementTurnsNotSeen ) // Begin tense music.... // unused //gfForceMusicToTense = TRUE; +#ifdef ENABLE_ZOMBIES UseCreatureMusic(HostileZombiesPresent()); +#endif + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); return( TRUE ); @@ -6682,8 +6692,10 @@ BOOLEAN CheckForEndOfBattle( BOOLEAN fAnEnemyRetreated ) return( FALSE ); } +#ifdef ENABLE_ZOMBIES if ( HostileZombiesPresent() ) //Madd: got tired of the victory music playing right after the zombies arose return FALSE; +#endif // OK, this is to releave infinate looping...becasue we can kill guys in this function if ( gfKillingGuysForLosingBattle ) @@ -7267,25 +7279,27 @@ UINT8 NumEnemyInSector( ) } -UINT8 NumZombiesInSector( ) -{ - SOLDIERTYPE *pTeamSoldier; - INT32 cnt = 0; - UINT8 ubNumZombies = 0; - - for ( pTeamSoldier = Menptr, cnt = 0; cnt < TOTAL_SOLDIERS; ++pTeamSoldier, ++cnt ) +#ifdef ENABLE_ZOMBIES + UINT8 NumZombiesInSector( ) { - if ( pTeamSoldier->bActive && pTeamSoldier->bInSector && pTeamSoldier->stats.bLife > 0 ) + SOLDIERTYPE *pTeamSoldier; + INT32 cnt = 0; + UINT8 ubNumZombies = 0; + + for ( pTeamSoldier = Menptr, cnt = 0; cnt < TOTAL_SOLDIERS; ++pTeamSoldier, ++cnt ) { - if ( pTeamSoldier->IsZombie() ) + if ( pTeamSoldier->bActive && pTeamSoldier->bInSector && pTeamSoldier->stats.bLife > 0 ) { - ++ubNumZombies; + if ( pTeamSoldier->IsZombie() ) + { + ++ubNumZombies; + } } } - } - return( ubNumZombies ); -} + return( ubNumZombies ); + } +#endif UINT8 NumEnemyInSectorExceptCreatures() { @@ -7799,9 +7813,11 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: loop = %d, numslots = %d ",uiLoop, guiNumMercSlots)); pSoldier = MercSlots[uiLoop]; +#ifdef ENABLE_ZOMBIES // Flugente: zombies do not receive any suppression at all! if ( pSoldier != NULL && pSoldier->IsZombie() ) continue; +#endif // Has this character received any Suppression Points since the last attack? // HEADROCK: Suppression Points accumulate by bullets flying near the character. It includes @@ -9286,28 +9302,30 @@ BOOLEAN HostileBloodcatsPresent( void ) return( FALSE ); } -BOOLEAN HostileZombiesPresent( void ) -{ - INT32 iLoop; - SOLDIERTYPE * pSoldier; - - if ( gTacticalStatus.Team[ CREATURE_TEAM ].bTeamActive == FALSE ) +#ifdef ENABLE_ZOMBIES + BOOLEAN HostileZombiesPresent( void ) { + INT32 iLoop; + SOLDIERTYPE * pSoldier; + + if ( gTacticalStatus.Team[ CREATURE_TEAM ].bTeamActive == FALSE ) + { + return( FALSE ); + } + + for ( iLoop = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID; iLoop <= gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; iLoop++ ) + { + pSoldier = MercPtrs[ iLoop ]; + + if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 && pSoldier->IsZombie() ) + { + return( TRUE ); + } + } + return( FALSE ); } - - for ( iLoop = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID; iLoop <= gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; iLoop++ ) - { - pSoldier = MercPtrs[ iLoop ]; - - if ( pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 && pSoldier->IsZombie() ) - { - return( TRUE ); - } - } - - return( FALSE ); -} +#endif void HandleCreatureTenseQuote( ) { diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index d7782cfc..335c5614 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -362,7 +362,10 @@ void DoPOWPathChecks( void ); BOOLEAN HostileCiviliansWithGunsPresent( void ); BOOLEAN HostileCiviliansPresent( void ); BOOLEAN HostileBloodcatsPresent( void ); -BOOLEAN HostileZombiesPresent( void ); + +#ifdef ENABLE_ZOMBIES + BOOLEAN HostileZombiesPresent( void ); +#endif UINT8 NumPCsInSector( void ); @@ -396,6 +399,9 @@ BOOLEAN WillAirRaidBeStopped( INT16 sSectorX, INT16 sSectorY ); // HEADROCK HAM 3.5: Externalized for First Arrival enemy check extern UINT8 NumEnemyInSector(); -// Flugente -extern UINT8 NumZombiesInSector(); +#ifdef ENABLE_ZOMBIES + // Flugente + extern UINT8 NumZombiesInSector(); +#endif + #endif \ No newline at end of file diff --git a/Tactical/Rotting Corpses.cpp b/Tactical/Rotting Corpses.cpp index 4ebc3f98..974c97e9 100644 --- a/Tactical/Rotting Corpses.cpp +++ b/Tactical/Rotting Corpses.cpp @@ -44,8 +44,7 @@ #include "qarray.h" #include "Interface.h" #include "Music Control.h" - #include "Campaign Types.h" - //#include "Queen Command.h" + #include "Campaign Types.h" #include "text.h" // added by Flugente #endif @@ -584,9 +583,11 @@ INT32 AddRottingCorpse( ROTTING_CORPSE_DEFINITION *pCorpseDef ) pCorpse->pAniTile->uiUserData = iIndex; pCorpse->iID = iIndex; +#ifdef ENABLE_ZOMBIES // copy name of corpse definition... memcpy( &(pCorpse->name), &(pCorpseDef->name), sizeof(CHAR16) * 10 ); pCorpse->name[9] = '\0'; +#endif pCorpse->fActivated = TRUE; @@ -861,6 +862,7 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE Corpse.usFlags |= ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE; } +#ifdef ENABLE_ZOMBIES // Flugente Zombies: Determine if a zombie can rise from this corpse switch ( gGameExternalOptions.sZombieRiseBehaviour ) { @@ -891,6 +893,7 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE // Flugente: copy name of soldier... memcpy( &(Corpse.name), &(pSoldier->name), sizeof(CHAR16) * 10 ); Corpse.name[9] = '\0'; +#endif // Determine corpse type! ubType = (UINT8)gubAnimSurfaceCorpseID[ pSoldier->ubBodyType][ pSoldier->usAnimState ]; @@ -2085,436 +2088,445 @@ UINT8 GetNearestRottingCorpseAIWarning( INT32 sGridNo ) return( ubHighestWarning ); } -// Flugente Zombies: resurrect zombies -void RaiseZombies( void ) -{ - if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) +#ifdef ENABLE_ZOMBIES + // Flugente Zombies: resurrect zombies + void RaiseZombies( void ) { - // if gGameExternalOptions.fZombieSpawnWaves is true, zombies will spawn from all corpses (while there is still room for more), creating a wave of zombies. with lots of bodies lying around, this can be a lot. - // if GameExternalOptions.fZombieSpawnWaves is false, each zombie can spawn randomly, you will get zombies on msot turns, but they won't spawn a whole horde at once - if ( ( gGameExternalOptions.fZombieSpawnWaves && (INT8) ( Random( 100 ) ) > 100 - gGameExternalOptions.sZombieRiseWaveFrequency ) || !gGameExternalOptions.fZombieSpawnWaves ) + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) { - ROTTING_CORPSE * pCorpse; - BOOLEAN zombieshaverisen = FALSE; - - SECTORINFO *pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ]; - - for ( INT32 cnt = giNumRottingCorpse - 1; cnt >= 0; --cnt ) + // if gGameExternalOptions.fZombieSpawnWaves is true, zombies will spawn from all corpses (while there is still room for more), creating a wave of zombies. with lots of bodies lying around, this can be a lot. + // if GameExternalOptions.fZombieSpawnWaves is false, each zombie can spawn randomly, you will get zombies on msot turns, but they won't spawn a whole horde at once + if ( ( gGameExternalOptions.fZombieSpawnWaves && (INT8) ( Random( 100 ) ) > 100 - gGameExternalOptions.sZombieRiseWaveFrequency ) || !gGameExternalOptions.fZombieSpawnWaves ) { - if ( pSector->ubNumCreatures < gGameExternalOptions.ubGameMaximumNumberOfCreatures ) // ... if there is still room for more zombies (zombies count as creatures until a separate ZOMBIE_TEAM is implemented)... + ROTTING_CORPSE * pCorpse; + BOOLEAN zombieshaverisen = FALSE; + + SECTORINFO *pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ]; + + for ( INT32 cnt = giNumRottingCorpse - 1; cnt >= 0; --cnt ) { - pCorpse = &(gRottingCorpse[ cnt ] ); - - // if zombies should spawn individually, roll for every corpse individually - if ( gGameExternalOptions.fZombieSpawnWaves || ( !gGameExternalOptions.fZombieSpawnWaves && (INT8) ( Random( 100 ) ) > 100 - gGameExternalOptions.sZombieRiseWaveFrequency ) ) + if ( pSector->ubNumCreatures < gGameExternalOptions.ubGameMaximumNumberOfCreatures ) // ... if there is still room for more zombies (zombies count as creatures until a separate ZOMBIE_TEAM is implemented)... { - if ( pCorpse->fActivated && pCorpse->def.fHeadTaken == FALSE ) // ... if corpse is active, and still has a head ... + pCorpse = &(gRottingCorpse[ cnt ] ); + + // if zombies should spawn individually, roll for every corpse individually + if ( gGameExternalOptions.fZombieSpawnWaves || ( !gGameExternalOptions.fZombieSpawnWaves && (INT8) ( Random( 100 ) ) > 100 - gGameExternalOptions.sZombieRiseWaveFrequency ) ) { - if ( !(pCorpse->def.usFlags & ROTTING_CORPSE_NEVER_RISE_AGAIN) ) // ... if corpse is already that of a zombie, don't create zombie again ... + if ( pCorpse->fActivated && pCorpse->def.fHeadTaken == FALSE ) // ... if corpse is active, and still has a head ... { - if ( !TileIsOutOfBounds(pCorpse->def.sGridNo) ) // ... if corpse is on existing coordinates ... - { - if ( WhoIsThere2( pCorpse->def.sGridNo, pCorpse->def.bLevel ) == NOBODY ) // ... if nobody else is on that position ... - { - UINT16 recanimstate = STANDING; + if ( !(pCorpse->def.usFlags & ROTTING_CORPSE_NEVER_RISE_AGAIN) ) // ... if corpse is already that of a zombie, don't create zombie again ... + { + if ( !TileIsOutOfBounds(pCorpse->def.sGridNo) ) // ... if corpse is on existing coordinates ... + { + if ( WhoIsThere2( pCorpse->def.sGridNo, pCorpse->def.bLevel ) == NOBODY ) // ... if nobody else is on that position ... + { + UINT16 recanimstate = STANDING; - if ( CorpseOkToSpawnZombie( pCorpse, &recanimstate ) ) // ... a zombie can be created from this corpse, in the corresponding animstate ... - { - zombieshaverisen = TRUE; - CreateZombiefromCorpse( pCorpse, recanimstate ); + if ( CorpseOkToSpawnZombie( pCorpse, &recanimstate ) ) // ... a zombie can be created from this corpse, in the corresponding animstate ... + { + zombieshaverisen = TRUE; + CreateZombiefromCorpse( pCorpse, recanimstate ); - //++pSector->ubNumZombies; - //++pSector->ubZombiesInBattle; + //++pSector->ubNumZombies; + //++pSector->ubZombiesInBattle; - RemoveCorpse( cnt ); + RemoveCorpse( cnt ); + } } } } } } } + else + { + // if there is no more room, we can skip this + break; + } } - else + + if ( zombieshaverisen ) { - // if there is no more room, we can skip this - break; + SetRenderFlags( RENDER_FLAG_FULL ); + + #ifdef JA2TESTVERSION + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"A wave of zombies is created"); + #endif + // Play sound + PlayJA2SampleFromFile( "Sounds\\zombie1.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN ); + + UseCreatureMusic(TRUE); // Madd: music when zombies rise + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); } } - - if ( zombieshaverisen ) - { - SetRenderFlags( RENDER_FLAG_FULL ); - -#ifdef JA2TESTVERSION - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"A wave of zombies is created"); -#endif - // Play sound - PlayJA2SampleFromFile( "Sounds\\zombie1.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN ); - - UseCreatureMusic(TRUE); // Madd: music when zombies rise - SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); - } } } -} -// Flugente Zombies 1.0: create a zombie from a corpse -void CreateZombiefromCorpse( ROTTING_CORPSE * pCorpse, UINT16 usAnimState ) -{ - SOLDIERCREATE_STRUCT MercCreateStruct; - - MercCreateStruct.ubProfile = NO_PROFILE; - MercCreateStruct.bTeam = CREATURE_TEAM; // should be ZOMBIE_TEAM, once that is properly implemented - MercCreateStruct.sInsertionGridNo = pCorpse->def.sGridNo; - MercCreateStruct.sSectorX = gWorldSectorX; - MercCreateStruct.sSectorY = gWorldSectorY; - MercCreateStruct.bSectorZ = gbWorldSectorZ; - MercCreateStruct.bBodyType = pCorpse->def.ubBodyType; - MercCreateStruct.ubDirection = pCorpse->def.ubDirection; - MercCreateStruct.fOnRoof = pCorpse->def.bLevel > 0 ? TRUE : FALSE; - - // add important stats according to difficulty level - // bLife is actually lower than bLifeMax. Because zombies have poison absorption, they can and will heal themselves over time, thereby gaining more life if they are not put down fast - switch( gGameExternalOptions.sZombieDifficultyLevel ) + // Flugente Zombies 1.0: create a zombie from a corpse + void CreateZombiefromCorpse( ROTTING_CORPSE * pCorpse, UINT16 usAnimState ) { - case 4: - MercCreateStruct.bLifeMax = (INT8)( 70 + Random( 30 ) ); - MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(10 + Random(15)); - MercCreateStruct.bAgility = (INT8)( 50 + Random( 10 ) ); - MercCreateStruct.bDexterity = (INT8)( 60 + Random( 15 ) ); - MercCreateStruct.bStrength = (INT8)( 80 + Random( 20 ) ); - break; + SOLDIERCREATE_STRUCT MercCreateStruct; + + MercCreateStruct.ubProfile = NO_PROFILE; + MercCreateStruct.bTeam = CREATURE_TEAM; // should be ZOMBIE_TEAM, once that is properly implemented + MercCreateStruct.sInsertionGridNo = pCorpse->def.sGridNo; + MercCreateStruct.sSectorX = gWorldSectorX; + MercCreateStruct.sSectorY = gWorldSectorY; + MercCreateStruct.bSectorZ = gbWorldSectorZ; + MercCreateStruct.bBodyType = pCorpse->def.ubBodyType; + MercCreateStruct.ubDirection = pCorpse->def.ubDirection; + MercCreateStruct.fOnRoof = pCorpse->def.bLevel > 0 ? TRUE : FALSE; - case 3: - MercCreateStruct.bLifeMax = (INT8)( 60 + Random( 20 ) ); - MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(10 + Random(10)); - MercCreateStruct.bAgility = (INT8)( 40 + Random( 10 ) ); - MercCreateStruct.bDexterity = (INT8)( 45 + Random( 10 ) ); - MercCreateStruct.bStrength = (INT8)( 60 + Random( 20 ) ); - break; - - case 2: - MercCreateStruct.bLifeMax = (INT8)( 45 + Random( 15 ) ); - MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(5 + Random(10)); - MercCreateStruct.bAgility = (INT8)( 30 + Random( 5 ) ); - MercCreateStruct.bDexterity = (INT8)( 30 + Random( 10 ) ); - MercCreateStruct.bStrength = (INT8)( 45 + Random( 20 ) ); - break; - - case 1: - default: - MercCreateStruct.bLifeMax = (INT8)( 35 + Random( 10 ) ); - MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(5 + Random(5)); - MercCreateStruct.bAgility = (INT8)( 15 + Random( 5 ) ); - MercCreateStruct.bDexterity = (INT8)( 15 + Random( 5 ) ); - MercCreateStruct.bStrength = (INT8)( 30 + Random( 20 ) ); - break; - } - - // FIX: something's wrong with the FATCIV-bodytype when dying... this is to ensure they die absolutely fast - //if ( MercCreateStruct.bBodyType == FATCIV ) - //MercCreateStruct.bLifeMax = OKLIFE; - - MercCreateStruct.bExpLevel = 1; - MercCreateStruct.bMarksmanship = 1; - MercCreateStruct.bMedical = 1; - MercCreateStruct.bMechanical = 1; - MercCreateStruct.bExplosive = 1; - MercCreateStruct.bLeadership = 1; - MercCreateStruct.bWisdom = 1; - MercCreateStruct.bMorale = 90; - MercCreateStruct.bAIMorale = MORALE_FEARLESS; - - SET_PALETTEREP_ID ( MercCreateStruct.HeadPal, pCorpse->def.HeadPal ); - SET_PALETTEREP_ID ( MercCreateStruct.PantsPal, pCorpse->def.PantsPal ); - SET_PALETTEREP_ID ( MercCreateStruct.VestPal, pCorpse->def.VestPal ); - SET_PALETTEREP_ID ( MercCreateStruct.SkinPal, pCorpse->def.SkinPal ); - - MercCreateStruct.fVisible = TRUE; - - INT8 iNewIndex; - if ( TacticalCreateSoldier( &MercCreateStruct, (UINT8 *)&iNewIndex ) ) - { - /* certain values have to be set afterwards - the alternative would be to edit each and every function that gets called from TacticalCreateSoldier() subsequently and - * make an exception for zombies every time... - */ - SOLDIERTYPE* pNewSoldier = MercPtrs[ (UINT8)iNewIndex ]; - - pNewSoldier->bActionPoints = 60; - pNewSoldier->bInitialActionPoints = 60; - pNewSoldier->sBreathRed = 0; - - pNewSoldier->ubInsertionDirection = pCorpse->def.ubDirection; - - //pNewSoldier->sHeightAdjustment = pCorpse->def.sHeightAdjustment; - pNewSoldier->sDesiredHeight = 3; - - pNewSoldier->ubDesiredHeight = 3; // this forces pNewSoldier to rise up to crouching position - - pNewSoldier->ubSoldierClass = SOLDIER_CLASS_ZOMBIE; - pNewSoldier->aiData.bOrders = SEEKENEMY; - pNewSoldier->aiData.bAttitude = AGGRESSIVE; - - ////////////// stuff for poisoning /////////////////////////////////// - // this is important - by declaring the gap between bLife and bLifeMax as bBleeding, the zombies will bleed (even more if they are damaged) - // all their lifepoints are also poisoned. Thereby all bleeding damage will be poisoned bleeding damage - // as they have bPoisonAbsorption of at least 200%, they will absorp this poison damage - // This leads to them GAINING life through bleeding - // They can thereby regain their health in battle (although not very fast) - pNewSoldier->bBleeding = pNewSoldier->stats.bLifeMax - pNewSoldier->stats.bLife; - - pNewSoldier->bPoisonSum = pNewSoldier->stats.bLifeMax; - pNewSoldier->bPoisonLife = pNewSoldier->stats.bLife; - pNewSoldier->bPoisonBleeding = pNewSoldier->bPoisonSum - pNewSoldier->bPoisonLife; - // zombies get 200% poison absorption, but no resistance to it, as it would reduce their healing - pNewSoldier->bPoisonResistance = 0; - pNewSoldier->bPoisonAbsorption = 200 + Random(100); - ////////////////////////////////////////////////////////////////////// - - if ( !memcmp( pCorpse->name, TacticalStr[ CIV_TEAM_MINER_NAME ], sizeof(pCorpse->name) ) - || !memcmp( pCorpse->name, TacticalStr[ MILITIA_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) - || !memcmp( pCorpse->name, TacticalStr[ CREATURE_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) - || !memcmp( pCorpse->name, TacticalStr[ CIV_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) - || !memcmp( pCorpse->name, TacticalStr[ ZOMBIE_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) - || !memcmp( pCorpse->name, TacticalStr[ ENEMY_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) ) - { - swprintf( pNewSoldier->name, TacticalStr[ ZOMBIE_TEAM_MERC_NAME ] ); - } - else - { - memcpy( &(pNewSoldier->name), &(pCorpse->name), sizeof(CHAR16) * 10 ); - pNewSoldier->name[9] = '\0'; - } - - // add skills according to difficulty level + // add important stats according to difficulty level + // bLife is actually lower than bLifeMax. Because zombies have poison absorption, they can and will heal themselves over time, thereby gaining more life if they are not put down fast switch( gGameExternalOptions.sZombieDifficultyLevel ) { case 4: - if ( gGameOptions.fNewTraitSystem ) - { - pNewSoldier->stats.ubSkillTraits[0] = MARTIAL_ARTS_NT; - pNewSoldier->stats.ubSkillTraits[1] = MARTIAL_ARTS_NT; - pNewSoldier->stats.ubSkillTraits[2] = ATHLETICS_NT; - } - else - { - pNewSoldier->stats.ubSkillTraits[0] = HANDTOHAND_OT; - pNewSoldier->stats.ubSkillTraits[1] = MARTIALARTS_OT; - } + MercCreateStruct.bLifeMax = (INT8)( 70 + Random( 30 ) ); + MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(10 + Random(15)); + MercCreateStruct.bAgility = (INT8)( 50 + Random( 10 ) ); + MercCreateStruct.bDexterity = (INT8)( 60 + Random( 15 ) ); + MercCreateStruct.bStrength = (INT8)( 80 + Random( 20 ) ); break; - + case 3: - if ( gGameOptions.fNewTraitSystem ) - { - pNewSoldier->stats.ubSkillTraits[0] = MARTIAL_ARTS_NT; - pNewSoldier->stats.ubSkillTraits[1] = ATHLETICS_NT; - pNewSoldier->stats.ubSkillTraits[2] = NO_SKILLTRAIT_NT; - } - else - { - pNewSoldier->stats.ubSkillTraits[0] = HANDTOHAND_OT; - pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_OT; - } + MercCreateStruct.bLifeMax = (INT8)( 60 + Random( 20 ) ); + MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(10 + Random(10)); + MercCreateStruct.bAgility = (INT8)( 40 + Random( 10 ) ); + MercCreateStruct.bDexterity = (INT8)( 45 + Random( 10 ) ); + MercCreateStruct.bStrength = (INT8)( 60 + Random( 20 ) ); break; case 2: - if ( gGameOptions.fNewTraitSystem ) - { - pNewSoldier->stats.ubSkillTraits[0] = MARTIAL_ARTS_NT; - pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_NT; - pNewSoldier->stats.ubSkillTraits[2] = NO_SKILLTRAIT_NT; - } - else - { - pNewSoldier->stats.ubSkillTraits[0] = NO_SKILLTRAIT_OT; - pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_OT; - } + MercCreateStruct.bLifeMax = (INT8)( 45 + Random( 15 ) ); + MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(5 + Random(10)); + MercCreateStruct.bAgility = (INT8)( 30 + Random( 5 ) ); + MercCreateStruct.bDexterity = (INT8)( 30 + Random( 10 ) ); + MercCreateStruct.bStrength = (INT8)( 45 + Random( 20 ) ); break; case 1: default: - if ( gGameOptions.fNewTraitSystem ) - { - pNewSoldier->stats.ubSkillTraits[0] = NO_SKILLTRAIT_NT; - pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_NT; - pNewSoldier->stats.ubSkillTraits[2] = NO_SKILLTRAIT_NT; - } - else - { - pNewSoldier->stats.ubSkillTraits[0] = NO_SKILLTRAIT_OT; - pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_OT; - } + MercCreateStruct.bLifeMax = (INT8)( 35 + Random( 10 ) ); + MercCreateStruct.bLife = MercCreateStruct.bLifeMax - (INT8)(5 + Random(5)); + MercCreateStruct.bAgility = (INT8)( 15 + Random( 5 ) ); + MercCreateStruct.bDexterity = (INT8)( 15 + Random( 5 ) ); + MercCreateStruct.bStrength = (INT8)( 30 + Random( 20 ) ); break; } - - AddSoldierToSectorNoCalculateDirectionUseAnimation( iNewIndex, usAnimState, 0 ); - // If this corpse has camo, use palette from hvobject - if ( pCorpse->def.ubType == ROTTING_STAGE2 ) - { - memcpy( pNewSoldier->p8BPPPalette, gpTileCache[ pCorpse->iCachedTileID ].pImagery->vo->pPaletteEntry, sizeof( pCorpse->p8BPPPalette ) * 256 ); - } - else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_CAMO_PALETTE ) - { - pNewSoldier->bCamo = 60; - pNewSoldier->wornCamo = 60; - } - else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_URBAN_CAMO_PALETTE ) - { - pNewSoldier->urbanCamo = 60; - pNewSoldier->wornUrbanCamo = 60; - } - else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_DESERT_CAMO_PALETTE ) - { - pNewSoldier->desertCamo = 60; - pNewSoldier->wornDesertCamo = 60; - } - else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE ) - { - pNewSoldier->snowCamo = 60; - pNewSoldier->wornSnowCamo = 60; - } + // FIX: something's wrong with the FATCIV-bodytype when dying... this is to ensure they die absolutely fast + //if ( MercCreateStruct.bBodyType == FATCIV ) + //MercCreateStruct.bLifeMax = OKLIFE; + + MercCreateStruct.bExpLevel = 1; + MercCreateStruct.bMarksmanship = 1; + MercCreateStruct.bMedical = 1; + MercCreateStruct.bMechanical = 1; + MercCreateStruct.bExplosive = 1; + MercCreateStruct.bLeadership = 1; + MercCreateStruct.bWisdom = 1; + MercCreateStruct.bMorale = 90; + MercCreateStruct.bAIMorale = MORALE_FEARLESS; + + SET_PALETTEREP_ID ( MercCreateStruct.HeadPal, pCorpse->def.HeadPal ); + SET_PALETTEREP_ID ( MercCreateStruct.PantsPal, pCorpse->def.PantsPal ); + SET_PALETTEREP_ID ( MercCreateStruct.VestPal, pCorpse->def.VestPal ); + SET_PALETTEREP_ID ( MercCreateStruct.SkinPal, pCorpse->def.SkinPal ); + + MercCreateStruct.fVisible = TRUE; - // Reload palettes.... - if ( pNewSoldier->bInSector ) + INT8 iNewIndex; + if ( TacticalCreateSoldier( &MercCreateStruct, (UINT8 *)&iNewIndex ) ) { - pNewSoldier->CreateSoldierPalettes( ); - } + /* certain values have to be set afterwards - the alternative would be to edit each and every function that gets called from TacticalCreateSoldier() subsequently and + * make an exception for zombies every time... + */ + SOLDIERTYPE* pNewSoldier = MercPtrs[ (UINT8)iNewIndex ]; + + pNewSoldier->bActionPoints = 60; + pNewSoldier->bInitialActionPoints = 60; + pNewSoldier->sBreathRed = 0; - // Set a pending animation to change stance first... - //SendChangeSoldierStanceEvent( pNewSoldier, ANIM_CROUCH ); + pNewSoldier->ubInsertionDirection = pCorpse->def.ubDirection; - // search for armour and equip if found - if ( gGameExternalOptions.fZombieRiseWithArmour ) - { - BOOLEAN fHelmetFound = FALSE; - BOOLEAN fVestFound = FALSE; - BOOLEAN fPantsFound = FALSE; + //pNewSoldier->sHeightAdjustment = pCorpse->def.sHeightAdjustment; + pNewSoldier->sDesiredHeight = 3; - ITEM_POOL * pItemPool, * pItemPoolNext; + pNewSoldier->ubDesiredHeight = 3; // this forces pNewSoldier to rise up to crouching position - GetItemPool( pCorpse->def.sGridNo, &pItemPool, pCorpse->def.bLevel ); + pNewSoldier->ubSoldierClass = SOLDIER_CLASS_ZOMBIE; + pNewSoldier->aiData.bOrders = SEEKENEMY; + pNewSoldier->aiData.bAttitude = AGGRESSIVE; - while( pItemPool && ( !fHelmetFound || !fVestFound ||!fPantsFound ) ) + ////////////// stuff for poisoning /////////////////////////////////// + // this is important - by declaring the gap between bLife and bLifeMax as bBleeding, the zombies will bleed (even more if they are damaged) + // all their lifepoints are also poisoned. Thereby all bleeding damage will be poisoned bleeding damage + // as they have bPoisonAbsorption of at least 200%, they will absorp this poison damage + // This leads to them GAINING life through bleeding + // They can thereby regain their health in battle (although not very fast) + pNewSoldier->bBleeding = pNewSoldier->stats.bLifeMax - pNewSoldier->stats.bLife; + + pNewSoldier->bPoisonSum = pNewSoldier->stats.bLifeMax; + pNewSoldier->bPoisonLife = pNewSoldier->stats.bLife; + pNewSoldier->bPoisonBleeding = pNewSoldier->bPoisonSum - pNewSoldier->bPoisonLife; + // zombies get 200% poison absorption, but no resistance to it, as it would reduce their healing + pNewSoldier->bPoisonResistance = 0; + pNewSoldier->bPoisonAbsorption = 200 + Random(100); + ////////////////////////////////////////////////////////////////////// + +#ifdef ENABLE_ZOMBIES + if ( !memcmp( pCorpse->name, TacticalStr[ CIV_TEAM_MINER_NAME ], sizeof(pCorpse->name) ) + || !memcmp( pCorpse->name, TacticalStr[ MILITIA_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) + || !memcmp( pCorpse->name, TacticalStr[ CREATURE_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) + || !memcmp( pCorpse->name, TacticalStr[ CIV_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) + || !memcmp( pCorpse->name, TacticalStr[ ZOMBIE_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) + || !memcmp( pCorpse->name, TacticalStr[ ENEMY_TEAM_MERC_NAME ], sizeof(pCorpse->name) ) ) { - pItemPoolNext = pItemPool->pNext; - - OBJECTTYPE* pObj = &(gWorldItems[ pItemPool->iItemIndex ].object); - - if ( Item[ pObj->usItem ].usItemClass == IC_ARMOUR ) - { - switch (Armour[Item[pObj->usItem].ubClassIndex].ubArmourClass) - { - case ARMOURCLASS_HELMET: - { - if( !fHelmetFound && AutoPlaceObject(pNewSoldier, pObj, FALSE)) - { - // remove item from the ground - RemoveItemFromPool( pCorpse->def.sGridNo, pItemPool->iItemIndex, pCorpse->def.bLevel ); - fHelmetFound = TRUE; - } - break; - } - case ARMOURCLASS_VEST: - { - if( !fVestFound && AutoPlaceObject(pNewSoldier, pObj, FALSE)) - { - // remove item from the ground - RemoveItemFromPool( pCorpse->def.sGridNo, pItemPool->iItemIndex, pCorpse->def.bLevel ); - fVestFound = TRUE; - } - break; - } - case ARMOURCLASS_LEGGINGS: - { - if( !fPantsFound && AutoPlaceObject(pNewSoldier, pObj, FALSE)) - { - // remove item from the ground - RemoveItemFromPool( pCorpse->def.sGridNo, pItemPool->iItemIndex, pCorpse->def.bLevel ); - fPantsFound = TRUE; - } - break; - } - default: - break; - } - } - - pItemPool = pItemPoolNext; + swprintf( pNewSoldier->name, TacticalStr[ ZOMBIE_TEAM_MERC_NAME ] ); } + else + { + memcpy( &(pNewSoldier->name), &(pCorpse->name), sizeof(CHAR16) * 10 ); + pNewSoldier->name[9] = '\0'; + } +#else + memcpy( &(pNewSoldier->name), &(pCorpse->name), sizeof(CHAR16) * 10 ); + pNewSoldier->name[9] = '\0'; +#endif + + // add skills according to difficulty level + switch( gGameExternalOptions.sZombieDifficultyLevel ) + { + case 4: + if ( gGameOptions.fNewTraitSystem ) + { + pNewSoldier->stats.ubSkillTraits[0] = MARTIAL_ARTS_NT; + pNewSoldier->stats.ubSkillTraits[1] = MARTIAL_ARTS_NT; + pNewSoldier->stats.ubSkillTraits[2] = ATHLETICS_NT; + } + else + { + pNewSoldier->stats.ubSkillTraits[0] = HANDTOHAND_OT; + pNewSoldier->stats.ubSkillTraits[1] = MARTIALARTS_OT; + } + break; + + case 3: + if ( gGameOptions.fNewTraitSystem ) + { + pNewSoldier->stats.ubSkillTraits[0] = MARTIAL_ARTS_NT; + pNewSoldier->stats.ubSkillTraits[1] = ATHLETICS_NT; + pNewSoldier->stats.ubSkillTraits[2] = NO_SKILLTRAIT_NT; + } + else + { + pNewSoldier->stats.ubSkillTraits[0] = HANDTOHAND_OT; + pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_OT; + } + break; + + case 2: + if ( gGameOptions.fNewTraitSystem ) + { + pNewSoldier->stats.ubSkillTraits[0] = MARTIAL_ARTS_NT; + pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_NT; + pNewSoldier->stats.ubSkillTraits[2] = NO_SKILLTRAIT_NT; + } + else + { + pNewSoldier->stats.ubSkillTraits[0] = NO_SKILLTRAIT_OT; + pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_OT; + } + break; + + case 1: + default: + if ( gGameOptions.fNewTraitSystem ) + { + pNewSoldier->stats.ubSkillTraits[0] = NO_SKILLTRAIT_NT; + pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_NT; + pNewSoldier->stats.ubSkillTraits[2] = NO_SKILLTRAIT_NT; + } + else + { + pNewSoldier->stats.ubSkillTraits[0] = NO_SKILLTRAIT_OT; + pNewSoldier->stats.ubSkillTraits[1] = NO_SKILLTRAIT_OT; + } + break; + } + + AddSoldierToSectorNoCalculateDirectionUseAnimation( iNewIndex, usAnimState, 0 ); + + // If this corpse has camo, use palette from hvobject + if ( pCorpse->def.ubType == ROTTING_STAGE2 ) + { + memcpy( pNewSoldier->p8BPPPalette, gpTileCache[ pCorpse->iCachedTileID ].pImagery->vo->pPaletteEntry, sizeof( pCorpse->p8BPPPalette ) * 256 ); + } + else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_CAMO_PALETTE ) + { + pNewSoldier->bCamo = 60; + pNewSoldier->wornCamo = 60; + } + else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_URBAN_CAMO_PALETTE ) + { + pNewSoldier->urbanCamo = 60; + pNewSoldier->wornUrbanCamo = 60; + } + else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_DESERT_CAMO_PALETTE ) + { + pNewSoldier->desertCamo = 60; + pNewSoldier->wornDesertCamo = 60; + } + else if ( pCorpse->def.usFlags & ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE ) + { + pNewSoldier->snowCamo = 60; + pNewSoldier->wornSnowCamo = 60; + } + + // Reload palettes.... + if ( pNewSoldier->bInSector ) + { + pNewSoldier->CreateSoldierPalettes( ); + } + + // Set a pending animation to change stance first... + //SendChangeSoldierStanceEvent( pNewSoldier, ANIM_CROUCH ); + +#ifdef ENABLE_ZOMBIES + // search for armour and equip if found + if ( gGameExternalOptions.fZombieRiseWithArmour ) + { + BOOLEAN fHelmetFound = FALSE; + BOOLEAN fVestFound = FALSE; + BOOLEAN fPantsFound = FALSE; + + ITEM_POOL * pItemPool, * pItemPoolNext; + + GetItemPool( pCorpse->def.sGridNo, &pItemPool, pCorpse->def.bLevel ); + + while( pItemPool && ( !fHelmetFound || !fVestFound ||!fPantsFound ) ) + { + pItemPoolNext = pItemPool->pNext; + + OBJECTTYPE* pObj = &(gWorldItems[ pItemPool->iItemIndex ].object); + + if ( Item[ pObj->usItem ].usItemClass == IC_ARMOUR ) + { + switch (Armour[Item[pObj->usItem].ubClassIndex].ubArmourClass) + { + case ARMOURCLASS_HELMET: + { + if( !fHelmetFound && AutoPlaceObject(pNewSoldier, pObj, FALSE)) + { + // remove item from the ground + RemoveItemFromPool( pCorpse->def.sGridNo, pItemPool->iItemIndex, pCorpse->def.bLevel ); + fHelmetFound = TRUE; + } + break; + } + case ARMOURCLASS_VEST: + { + if( !fVestFound && AutoPlaceObject(pNewSoldier, pObj, FALSE)) + { + // remove item from the ground + RemoveItemFromPool( pCorpse->def.sGridNo, pItemPool->iItemIndex, pCorpse->def.bLevel ); + fVestFound = TRUE; + } + break; + } + case ARMOURCLASS_LEGGINGS: + { + if( !fPantsFound && AutoPlaceObject(pNewSoldier, pObj, FALSE)) + { + // remove item from the ground + RemoveItemFromPool( pCorpse->def.sGridNo, pItemPool->iItemIndex, pCorpse->def.bLevel ); + fPantsFound = TRUE; + } + break; + } + default: + break; + } + } + + pItemPool = pItemPoolNext; + } + } +#endif + + // Change to standing,unless we can getup with an animation + pNewSoldier->EVENT_InitNewSoldierAnim( STANDING, 0, TRUE ); + pNewSoldier->BeginSoldierGetup( ); + + // So we can see them! + AllTeamsLookForAll(ALLOW_INTERRUPTS); + } + } + + // Flugente Zombies: returns true if a zombie can be raised from this corpse, and returns the correct pAnimState for the new zombie + BOOLEAN CorpseOkToSpawnZombie( ROTTING_CORPSE * pCorpse, UINT16* pAnimState ) + { + BOOLEAN canbezombie = FALSE; + *pAnimState = FALLBACKHIT_STOP; + + switch ( pCorpse->def.ubType ) + { + case SMERC_BCK: + case SMERC_DHD: + case SMERC_FALL: + case MMERC_BCK: + case MMERC_DHD: + case MMERC_FALL: + case FMERC_BCK: + case FMERC_DHD: + case FMERC_FALL: + case ROTTING_STAGE2: + canbezombie = TRUE; + *pAnimState = FALLBACKHIT_STOP; + break; + + case SMERC_FWD: + case SMERC_PRN: + case SMERC_FALLF: + case MMERC_FWD: + case MMERC_PRN: + case MMERC_FALLF: + case FMERC_FWD: + case FMERC_PRN: + case FMERC_FALLF: + canbezombie = TRUE; + *pAnimState = FALLFORWARD_HITDEATH_STOP; + break; + + case M_DEAD1: + canbezombie = TRUE; + *pAnimState = GENERIC_HIT_DEATH; + break; + + case M_DEAD2: + canbezombie = TRUE; + *pAnimState = CIV_DIE2; + + case H_DEAD1: + case K_DEAD1: + case FT_DEAD1: + case S_DEAD1: + case W_DEAD1: + case C_DEAD1: + case H_DEAD2: + case K_DEAD2: + case FT_DEAD2: + case S_DEAD2: + case W_DEAD2: + case C_DEAD2: + canbezombie = TRUE; + *pAnimState = STANDING; + + default: + ; } - // Change to standing,unless we can getup with an animation - pNewSoldier->EVENT_InitNewSoldierAnim( STANDING, 0, TRUE ); - pNewSoldier->BeginSoldierGetup( ); - - // So we can see them! - AllTeamsLookForAll(ALLOW_INTERRUPTS); + return( canbezombie ); } -} - -// Flugente Zombies: returns true if a zombie can be raised from this corpse, and returns the correct pAnimState for the new zombie -BOOLEAN CorpseOkToSpawnZombie( ROTTING_CORPSE * pCorpse, UINT16* pAnimState ) -{ - BOOLEAN canbezombie = FALSE; - *pAnimState = FALLBACKHIT_STOP; - - switch ( pCorpse->def.ubType ) - { - case SMERC_BCK: - case SMERC_DHD: - case SMERC_FALL: - case MMERC_BCK: - case MMERC_DHD: - case MMERC_FALL: - case FMERC_BCK: - case FMERC_DHD: - case FMERC_FALL: - case ROTTING_STAGE2: - canbezombie = TRUE; - *pAnimState = FALLBACKHIT_STOP; - break; - - case SMERC_FWD: - case SMERC_PRN: - case SMERC_FALLF: - case MMERC_FWD: - case MMERC_PRN: - case MMERC_FALLF: - case FMERC_FWD: - case FMERC_PRN: - case FMERC_FALLF: - canbezombie = TRUE; - *pAnimState = FALLFORWARD_HITDEATH_STOP; - break; - - case M_DEAD1: - canbezombie = TRUE; - *pAnimState = GENERIC_HIT_DEATH; - break; - - case M_DEAD2: - canbezombie = TRUE; - *pAnimState = CIV_DIE2; - - case H_DEAD1: - case K_DEAD1: - case FT_DEAD1: - case S_DEAD1: - case W_DEAD1: - case C_DEAD1: - case H_DEAD2: - case K_DEAD2: - case FT_DEAD2: - case S_DEAD2: - case W_DEAD2: - case C_DEAD2: - canbezombie = TRUE; - *pAnimState = STANDING; - - default: - ; - } - - return( canbezombie ); -} +#endif diff --git a/Tactical/Rotting Corpses.h b/Tactical/Rotting Corpses.h index bd7c7dc1..34745e82 100644 --- a/Tactical/Rotting Corpses.h +++ b/Tactical/Rotting Corpses.h @@ -84,7 +84,10 @@ enum RottingCorpseDefines #define ROTTING_CORPSE_USE_URBAN_CAMO_PALETTE 0x100 //We use urban palette here.... #define ROTTING_CORPSE_USE_DESERT_CAMO_PALETTE 0x200 //We use desert palette here.... #define ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE 0x400 //We use snow palette here.... -#define ROTTING_CORPSE_NEVER_RISE_AGAIN 0x800 //a zombie cannot be created from this corpse (if not set, it'll eventually rise again ) + +#ifdef ENABLE_ZOMBIES + #define ROTTING_CORPSE_NEVER_RISE_AGAIN 0x800 //a zombie cannot be created from this corpse (if not set, it'll eventually rise again ) +#endif typedef struct @@ -116,8 +119,10 @@ typedef struct UINT8 ubFiller[ 12 ]; +#ifdef ENABLE_ZOMBIES // Flugente: added name so we can display individual name if corpse gets resurrected... CHAR16 name[ 10 ]; +#endif } ROTTING_CORPSE_DEFINITION; @@ -140,8 +145,10 @@ typedef struct BOOLEAN fAttractCrowsOnlyWhenOnScreen; INT32 iID; +#ifdef ENABLE_ZOMBIES // Flugente: added name so we can display individual name if corpse gets resurrected... CHAR16 name[ 10 ]; +#endif } ROTTING_CORPSE; @@ -200,13 +207,15 @@ INT32 GetGridNoOfCorpseGivenProfileID( UINT8 ubProfileID ); void DecayRottingCorpseAIWarnings( void ); UINT8 GetNearestRottingCorpseAIWarning( INT32 sGridNo ); -// Flugente: Raise zombies -void RaiseZombies( void ); +#ifdef ENABLE_ZOMBIES + // Flugente: Raise zombies + void RaiseZombies( void ); -// Flugente: create a zombie from a corpse -void CreateZombiefromCorpse( ROTTING_CORPSE * pCorpse, UINT16 usAnimState ); + // Flugente: create a zombie from a corpse + void CreateZombiefromCorpse( ROTTING_CORPSE * pCorpse, UINT16 usAnimState ); -// Flugente: returns true if a zombie can be raised from this corpse, and returns the correct pAnimState for the new zombie -BOOLEAN CorpseOkToSpawnZombie( ROTTING_CORPSE * pCorpse, UINT16* pAnimState ); + // Flugente: returns true if a zombie can be raised from this corpse, and returns the correct pAnimState for the new zombie + BOOLEAN CorpseOkToSpawnZombie( ROTTING_CORPSE * pCorpse, UINT16* pAnimState ); +#endif #endif \ No newline at end of file diff --git a/Tactical/Soldier Ani.cpp b/Tactical/Soldier Ani.cpp index 4d96c0e3..c7ad9866 100644 --- a/Tactical/Soldier Ani.cpp +++ b/Tactical/Soldier Ani.cpp @@ -3485,9 +3485,11 @@ BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse ) case SOLDIER_CLASS_CREATURE : gMercProfiles[ MercPtrs[ ubAttacker ]->ubProfile ].records.usKillsCreatures++; break; +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE : gMercProfiles[ MercPtrs[ ubAttacker ]->ubProfile ].records.usKillsZombies++; break; +#endif default : if ( CREATURE_OR_BLOODCAT( pSoldier ) ) gMercProfiles[ MercPtrs[ ubAttacker ]->ubProfile ].records.usKillsCreatures++; diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index a281dd5c..fc7d53fd 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -137,8 +137,10 @@ extern INT16 DirIncrementer[8]; #define MIN_SUBSEQUENT_SNDS_DELAY 2000 #include "connect.h" -extern void TeleportSelectedSoldier( void ); -extern BOOLEAN AddSoldierToSectorNoCalculateDirectionUseAnimation( UINT8 ubID, UINT16 usAnimState, UINT16 usAnimCode ); +#ifdef ENABLE_ZOMBIES + extern void TeleportSelectedSoldier( void ); + extern BOOLEAN AddSoldierToSectorNoCalculateDirectionUseAnimation( UINT8 ubID, UINT16 usAnimState, UINT16 usAnimCode ); +#endif // Enumerate extended directions @@ -1349,7 +1351,11 @@ MERCPROFILESTRUCT& MERCPROFILESTRUCT::operator=(const OLD_MERCPROFILESTRUCT_101& this->records.usKillsAdmins = (src.usKills/4); this->records.usKillsHostiles = 0; this->records.usKillsCreatures = 0; + +#ifdef ENABLE_ZOMBIES this->records.usKillsZombies = 0; +#endif + this->records.usKillsTanks = 0; this->records.usKillsOthers = 0; this->records.usAssistsMercs = (src.usAssists*3/4); @@ -8673,6 +8679,7 @@ void SOLDIERTYPE::BeginSoldierClimbUpRoof( void ) this->ubPendingDirection = bNewDirection; //this->usPendingAnimation = CLIMBUPROOF; + #ifdef ENABLE_ZOMBIES // Flugente: In case an animation is missing (zombies with bodytype of civilians), we TELEPORT instead if ( IsAnimationValidForBodyType( this, CLIMBUPROOF ) == FALSE ) { @@ -8682,6 +8689,9 @@ void SOLDIERTYPE::BeginSoldierClimbUpRoof( void ) } else this->EVENT_InitNewSoldierAnim( CLIMBUPROOF, 0 , FALSE ); +#else + this->EVENT_InitNewSoldierAnim( CLIMBUPROOF, 0 , FALSE ); +#endif this->InternalReceivingSoldierCancelServices( FALSE ); this->InternalGivingSoldierCancelServices( FALSE ); @@ -9276,7 +9286,11 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sPo // ATE: Put some logic in here to allow enemies to die quicker..... // Are we an enemy? // zombies don't die suddenly, as they regenerate health by bloodloss and poison. You have to make sure they die! +#ifdef ENABLE_ZOMBIES if ( this->bSide != gbPlayerNum && !this->aiData.bNeutral && this->ubProfile == NO_PROFILE && !this->IsZombie() ) +#else + if ( this->bSide != gbPlayerNum && !this->aiData.bNeutral && this->ubProfile == NO_PROFILE) +#endif { // ATE: Give them a chance to fall down... if ( this->stats.bLife > 0 && this->stats.bLife < ( OKLIFE - 1 ) ) @@ -9310,12 +9324,15 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sPo } } } + +#ifdef ENABLE_ZOMBIES else if ( this->IsZombie() && this->stats.bLife > 0 && this->stats.bLife < OKLIFE ) { // a zombie doesn't automatically die, so he would normally stand up again after being hit. // We don't want that, because he is dying, so we manually skip that animation this->usPendingAnimation = NO_PENDING_ANIMATION; } +#endif if ( fShowDamage ) { @@ -9988,6 +10005,7 @@ BOOLEAN SOLDIERTYPE::InternalDoMercBattleSound( UINT8 ubBattleSoundID, INT8 bSpe } } } +#ifdef ENABLE_ZOMBIES else if ( pSoldier->IsZombie() ) // Madd: add zombie sounds { if ( ubSoundID == BATTLE_SOUND_DIE1 ) @@ -10007,6 +10025,7 @@ BOOLEAN SOLDIERTYPE::InternalDoMercBattleSound( UINT8 ubBattleSoundID, INT8 bSpe } } } +#endif else { if ( ubSoundID == BATTLE_SOUND_DIE1 ) @@ -10265,6 +10284,7 @@ void SOLDIERTYPE::BeginSoldierClimbDownRoof( void ) this->ubPendingDirection = bNewDirection; +#ifdef ENABLE_ZOMBIES // Flugente: In case an animation is missing (zombies with bodytype of civilians), we TELEPORT instead if ( IsAnimationValidForBodyType( this, JUMPDOWNWALL ) == FALSE ) { @@ -10274,6 +10294,9 @@ void SOLDIERTYPE::BeginSoldierClimbDownRoof( void ) } else this->EVENT_InitNewSoldierAnim( JUMPDOWNWALL, 0 , FALSE ); +#else + this->EVENT_InitNewSoldierAnim( JUMPDOWNWALL, 0 , FALSE ); +#endif this->InternalReceivingSoldierCancelServices( FALSE ); this->InternalGivingSoldierCancelServices( FALSE ); @@ -11505,7 +11528,13 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio #if (defined JA2UB || defined JA113NODEMO) if ( fMartialArtist && !Item[usItem].crowbar && this->ubBodyType == REGMALE) #else - if ( fMartialArtist && !AreInMeanwhile( ) && !Item[usItem].crowbar && this->ubBodyType == REGMALE && !IsZombie() ) // SANDRO - added check for body type + + #ifdef ENABLE_ZOMBIES + if ( fMartialArtist && !AreInMeanwhile( ) && !Item[usItem].crowbar && this->ubBodyType == REGMALE && !IsZombie() ) // SANDRO - added check for body type + #else + if ( fMartialArtist && !AreInMeanwhile( ) && !Item[usItem].crowbar && this->ubBodyType == REGMALE ) // SANDRO - added check for body type + #endif + #endif { // Are we in attack mode yet? @@ -11520,6 +11549,7 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio } else { +#ifdef ENABLE_ZOMBIES // Flugente: civilians can be zombies too, but they do not have a 'punch' animation. Simple fix: They 'attack' without animation... // CHECK IF WE CAN DO THIS ANIMATION! if ( this->IsZombie() && IsAnimationValidForBodyType( this, PUNCH ) == FALSE ) @@ -11589,6 +11619,7 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio } else { +#endif // Look at stance of target switch( gAnimControl[ pTSoldier->usAnimState ].ubEndHeight ) { @@ -11649,7 +11680,10 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio } } break; + +#ifdef ENABLE_ZOMBIES } +#endif } } @@ -13402,6 +13436,8 @@ INT32 SOLDIERTYPE::GetDamageResistance(BOOLEAN fAutoResolve, BOOLEAN fCalcBreath resistance += gGameExternalOptions.sEnemyRegularDamageResistance; else if (this->ubSoldierClass == SOLDIER_CLASS_ELITE && gGameExternalOptions.sEnemyEliteDamageResistance != 0) resistance += gGameExternalOptions.sEnemyEliteDamageResistance; + +#ifdef ENABLE_ZOMBIES else if (IsZombie()) { if ( fCalcBreathLoss ) @@ -13409,6 +13445,7 @@ INT32 SOLDIERTYPE::GetDamageResistance(BOOLEAN fAutoResolve, BOOLEAN fCalcBreath else resistance += gGameExternalOptions.sEnemyZombieDamageResistance; } +#endif } ////////////////////////////////////////////////////////////////////////////////////// @@ -13463,10 +13500,13 @@ INT16 SOLDIERTYPE::GetSoldierCriticalDamageBonus( void ) return val; } -BOOLEAN SOLDIERTYPE::IsZombie( void ) -{ - return( ubSoldierClass == SOLDIER_CLASS_ZOMBIE ); -} + +#ifdef ENABLE_ZOMBIES + BOOLEAN SOLDIERTYPE::IsZombie( void ) + { + return( ubSoldierClass == SOLDIER_CLASS_ZOMBIE ); + } +#endif INT16 SOLDIERTYPE::GetPoisonResistance( void ) { @@ -13497,9 +13537,11 @@ INT16 SOLDIERTYPE::GetPoisonDamagePercentage( void ) // Flugente: this percentage has to be between 0% and 100% INT16 val = 0; +#ifdef ENABLE_ZOMBIES // zombies poison damage percentage is externalised if ( IsZombie() ) val += gGameExternalOptions.sZombiePoisonDamagePercentage; +#endif if ( this->usAttackingWeapon ) { diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 9be10be7..92c31ef3 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -310,7 +310,9 @@ enum SOLDIER_CLASS_ELITE_MILITIA, SOLDIER_CLASS_CREATURE, SOLDIER_CLASS_MINER, +#ifdef ENABLE_ZOMBIES SOLDIER_CLASS_ZOMBIE, +#endif }; #define SOLDIER_CLASS_ENEMY( bSoldierClass ) ( ( bSoldierClass >= SOLDIER_CLASS_ADMINISTRATOR ) && ( bSoldierClass <= SOLDIER_CLASS_ARMY ) ) @@ -1358,8 +1360,10 @@ public: INT16 GetSoldierCriticalDamageBonus( void ); // Flugente: determines critical damage bonus depending on class, skill, etc. +#ifdef ENABLE_ZOMBIES // Flugente: Zombies BOOLEAN IsZombie( void ); +#endif // Flugente: poison system // These functions might one day be modified by traits etc. We'll keep that in these functions and not clutter the rest of the code diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index 70342564..1afca45e 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -1729,10 +1729,12 @@ BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREAT { swprintf( pSoldier->name, gzLateLocalizedString[ 36 ] ); } +#ifdef ENABLE_ZOMBIES else if ( pSoldier->IsZombie() ) { swprintf( pSoldier->name, TacticalStr[ ZOMBIE_TEAM_MERC_NAME ] ); } +#endif else { swprintf( pSoldier->name, TacticalStr[ CREATURE_TEAM_MERC_NAME ] ); break; @@ -2176,7 +2178,9 @@ void CreateDetailedPlacementGivenBasicPlacementInfo( SOLDIERCREATE_STRUCT *pp, B switch( ubSoldierClass ) { case SOLDIER_CLASS_ADMINISTRATOR: +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE: +#endif pp->bExpLevel = (INT8) 2 + bExpLevelModifier; break; case SOLDIER_CLASS_ARMY: @@ -2294,7 +2298,11 @@ void CreateDetailedPlacementGivenBasicPlacementInfo( SOLDIERCREATE_STRUCT *pp, B //If it is a detailed placement, don't do this yet, as detailed placements may have their //own equipment. DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CreateDetailedPlacementGivenBasicPlacementInfo: generate random equipment")); +#ifdef ENABLE_ZOMBIES if( !bp->fDetailedPlacement && ubSoldierClass != SOLDIER_CLASS_NONE && ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_MINER && ubSoldierClass != SOLDIER_CLASS_ZOMBIE ) +#else + if( !bp->fDetailedPlacement && ubSoldierClass != SOLDIER_CLASS_NONE && ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_MINER ) +#endif GenerateRandomEquipment( pp, ubSoldierClass, bp->bRelativeEquipmentLevel); DecideToAssignSniperOrders(pp); @@ -2462,7 +2470,12 @@ void CreateDetailedPlacementGivenStaticDetailedPlacementAndBasicPlacementInfo( //{ // ReplaceExtendedGuns( pp, bp->ubSoldierClass ); //} + +#ifdef ENABLE_ZOMBIES if( bp->ubSoldierClass != SOLDIER_CLASS_NONE && bp->ubSoldierClass != SOLDIER_CLASS_CREATURE && bp->ubSoldierClass != SOLDIER_CLASS_MINER && bp->ubSoldierClass != SOLDIER_CLASS_ZOMBIE ) +#else + if( bp->ubSoldierClass != SOLDIER_CLASS_NONE && bp->ubSoldierClass != SOLDIER_CLASS_CREATURE && bp->ubSoldierClass != SOLDIER_CLASS_MINER ) +#endif { GenerateRandomEquipment( pp, bp->ubSoldierClass, bp->bRelativeEquipmentLevel); DecideToAssignSniperOrders(pp); @@ -2657,7 +2670,11 @@ SOLDIERTYPE* ReserveTacticalSoldierForAutoresolve( UINT8 ubSoldierClass ) //returns the pointer to that soldier. This is used when copying the exact status of //all remaining enemy troops (or creatures) to finish the battle in autoresolve. To //signify that the troop has already been reserved, we simply set their gridno to NOWHERE. +#ifdef ENABLE_ZOMBIES if( ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_ZOMBIE ) +#else + if( ubSoldierClass != SOLDIER_CLASS_CREATURE ) +#endif { //use the enemy team iStart = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; iEnd = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; @@ -2799,39 +2816,41 @@ SOLDIERTYPE* TacticalCreateEliteEnemy() return( pSoldier ); } -//USED BY STRATEGIC AI and AUTORESOLVE -SOLDIERTYPE* TacticalCreateZombie() -{ - BASIC_SOLDIERCREATE_STRUCT bp; - SOLDIERCREATE_STRUCT pp; - UINT8 ubID; - SOLDIERTYPE * pSoldier; - - if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI ) +#ifdef ENABLE_ZOMBIES + //USED BY STRATEGIC AI and AUTORESOLVE + SOLDIERTYPE* TacticalCreateZombie() { - pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_ZOMBIE ); - if( pSoldier ) return pSoldier; - } + BASIC_SOLDIERCREATE_STRUCT bp; + SOLDIERCREATE_STRUCT pp; + UINT8 ubID; + SOLDIERTYPE * pSoldier; - memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) ); - RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_ZOMBIE ); - RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_ZOMBIE ); - bp.bTeam = CREATURE_TEAM;//ZOMBIE_TEAM; - bp.bOrders = SEEKENEMY; - bp.bAttitude = AGGRESSIVE; - bp.bBodyType = -1; - bp.ubSoldierClass = SOLDIER_CLASS_ZOMBIE; - CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp ); - pSoldier = TacticalCreateSoldier( &pp, &ubID ); - if ( pSoldier ) - { - // send soldier to centre of map, roughly - pSoldier->aiData.sNoiseGridno = (CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS); - pSoldier->aiData.ubNoiseVolume = MAX_MISC_NOISE_DURATION; - } + if( guiCurrentScreen == AUTORESOLVE_SCREEN && !gfPersistantPBI ) + { + pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_ZOMBIE ); + if( pSoldier ) return pSoldier; + } - return( pSoldier ); -} + memset( &bp, 0, sizeof( BASIC_SOLDIERCREATE_STRUCT ) ); + RandomizeRelativeLevel( &( bp.bRelativeAttributeLevel ), SOLDIER_CLASS_ZOMBIE ); + RandomizeRelativeLevel( &( bp.bRelativeEquipmentLevel ), SOLDIER_CLASS_ZOMBIE ); + bp.bTeam = CREATURE_TEAM;//ZOMBIE_TEAM; + bp.bOrders = SEEKENEMY; + bp.bAttitude = AGGRESSIVE; + bp.bBodyType = -1; + bp.ubSoldierClass = SOLDIER_CLASS_ZOMBIE; + CreateDetailedPlacementGivenBasicPlacementInfo( &pp, &bp ); + pSoldier = TacticalCreateSoldier( &pp, &ubID ); + if ( pSoldier ) + { + // send soldier to centre of map, roughly + pSoldier->aiData.sNoiseGridno = (CENTRAL_GRIDNO + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) + ( Random( CENTRAL_RADIUS * 2 + 1 ) - CENTRAL_RADIUS ) * WORLD_COLS); + pSoldier->aiData.ubNoiseVolume = MAX_MISC_NOISE_DURATION; + } + + return( pSoldier ); + } +#endif SOLDIERTYPE* ReserveTacticalMilitiaSoldierForAutoresolve( UINT8 ubSoldierClass ) { @@ -4236,8 +4255,10 @@ BOOLEAN AssignTraitsToSoldier( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCre iChance = 10 + ubProgress/4; // 10-35% chance else if( ubSolClass == SOLDIER_CLASS_ADMINISTRATOR || ubSolClass == SOLDIER_CLASS_GREEN_MILITIA ) iChance = ubProgress/4; // 0-25% chance +#ifdef ENABLE_ZOMBIES else if (ubSolClass == SOLDIER_CLASS_ZOMBIE ) iChance = 100; // 100% chance +#endif if( foundHtH ) // if found brass knuckless, increase the chance (doesn't happen often) iChance += 35; @@ -4281,8 +4302,10 @@ BOOLEAN AssignTraitsToSoldier( SOLDIERTYPE *pSoldier, SOLDIERCREATE_STRUCT *pCre iChance = 25 + ubProgress*2/5; // 25-65% chance else if( ubSolClass == SOLDIER_CLASS_ADMINISTRATOR || ubSolClass == SOLDIER_CLASS_GREEN_MILITIA ) iChance = 10 + ubProgress*2/5; // 10-50% chance +#ifdef ENABLE_ZOMBIES else if (ubSolClass == SOLDIER_CLASS_ZOMBIE ) iChance = 100; // 100% chance +#endif // modify the chance by preset ini setting if( gGameExternalOptions.bAssignedTraitsRarity != 0 ) diff --git a/Tactical/Soldier Create.h b/Tactical/Soldier Create.h index d2c7efba..165cea8c 100644 --- a/Tactical/Soldier Create.h +++ b/Tactical/Soldier Create.h @@ -420,7 +420,9 @@ SOLDIERTYPE* TacticalCreateSoldier( SOLDIERCREATE_STRUCT *pCreateStruct, UINT8 * SOLDIERTYPE* TacticalCreateAdministrator(); SOLDIERTYPE* TacticalCreateEliteEnemy(); SOLDIERTYPE* TacticalCreateArmyTroop(); -SOLDIERTYPE* TacticalCreateZombie(); // Flugente Zombies +#ifdef ENABLE_ZOMBIES + SOLDIERTYPE* TacticalCreateZombie(); // Flugente Zombies +#endif SOLDIERTYPE* TacticalCreateMilitia( UINT8 ubMilitiaClass ); SOLDIERTYPE* TacticalCreateCreature( INT8 bCreatureBodyType ); diff --git a/Tactical/Tactical Turns.cpp b/Tactical/Tactical Turns.cpp index 7a83432c..cb2b7347 100644 --- a/Tactical/Tactical Turns.cpp +++ b/Tactical/Tactical Turns.cpp @@ -275,11 +275,14 @@ void HandleTacticalEndTurn( ) // Flugente: Cool down all items not in a soldier's inventory CoolDownWorldItems( FALSE ); + +#ifdef ENABLE_ZOMBIES // Flugente: raise zombies if in gamescreen and option set if ( guiCurrentScreen == GAME_SCREEN ) { RaiseZombies(); } +#endif } diff --git a/Tactical/TeamTurns.cpp b/Tactical/TeamTurns.cpp index 55895647..ab58fa89 100644 --- a/Tactical/TeamTurns.cpp +++ b/Tactical/TeamTurns.cpp @@ -464,11 +464,14 @@ void EndTurnEvents( void ) // Flugente: Cool down all items not in a soldier's inventory CoolDownWorldItems( FALSE ); + +#ifdef ENABLE_ZOMBIES // Flugente: raise zombies if in gamescreen and option set if ( guiCurrentScreen == GAME_SCREEN ) { RaiseZombies(); } +#endif // decay AI warning values from corpses DecayRottingCorpseAIWarnings(); diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 7dc85034..9149eb3f 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -8635,12 +8635,14 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, BULLET *pBullet, SOLDIERTYPE * pTarget, INT8 bStatLoss = 0; UINT8 ubAmmoType; +#ifdef ENABLE_ZOMBIES if ( pTarget->IsZombie() && gGameExternalOptions.fZombieOnlyHeadshotsWork ) { // if bullet does not hits anything other than the head, it doesn't do any damage if ( ubHitLocation != AIM_SHOT_HEAD ) return 0; } +#endif // NOTE: reduction of bullet impact due to range and obstacles is handled // in MoveBullet. diff --git a/Tactical/opplist.cpp b/Tactical/opplist.cpp index bb24dec1..9167f634 100644 --- a/Tactical/opplist.cpp +++ b/Tactical/opplist.cpp @@ -4764,7 +4764,11 @@ void DebugSoldierPage4( ) case SOLDIER_CLASS_REG_MILITIA: gprintf( 320, LINE_HEIGHT * ubLine, L"(Reg Militia)" ); break; case SOLDIER_CLASS_ELITE_MILITIA: gprintf( 320, LINE_HEIGHT * ubLine, L"(Elite Militia)" ); break; case SOLDIER_CLASS_MINER: gprintf( 320, LINE_HEIGHT * ubLine, L"(Miner)" ); break; + +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE: gprintf( 320, LINE_HEIGHT * ubLine, L"(Zombie)" ); break; +#endif + default: break; //don't care (don't write anything) } ubLine++; diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index dc4275eb..9b43c07c 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -172,12 +172,14 @@ void CreatureCall( SOLDIERTYPE * pCaller ); INT8 CreatureDecideAction( SOLDIERTYPE * pCreature ); void CreatureDecideAlertStatus( SOLDIERTYPE *pCreature ); -// Flugente: Zombie AI -INT8 ZombieDecideAction( SOLDIERTYPE * pZombie ); -void ZombieDecideAlertStatus( SOLDIERTYPE *pZombie ); +#ifdef ENABLE_ZOMBIES + // Flugente: Zombie AI + INT8 ZombieDecideAction( SOLDIERTYPE * pZombie ); + void ZombieDecideAlertStatus( SOLDIERTYPE *pZombie ); -// a variant of ClosestSeenOpponent(...), that allows zombies -INT32 ClosestSeenOpponentforZombie(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel); + // a variant of ClosestSeenOpponent(...), that allows zombies + INT32 ClosestSeenOpponentforZombie(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel); +#endif INT8 CrowDecideAction( SOLDIERTYPE * pSoldier ); void DecideAlertStatus( SOLDIERTYPE *pSoldier ); @@ -241,9 +243,11 @@ INT8 FindDirectionForClimbing( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel // HEADROCK HAM B2.7: Functions to assist group AI // WANNE: Headrock informed me that I should disable these 3 functions in code, because they need a lot of CPU during AI calculation. +/* INT16 AssessTacticalSituation( INT8 bSide ); BOOLEAN TeamSeesOpponent( INT8 bSide, SOLDIERTYPE * pOpponent ); INT32 CalcStraightThreatValue( SOLDIERTYPE *pEnemy ); +*/ // SANDRO - added following functions BOOLEAN DoctorIsPresent( SOLDIERTYPE * pPatient, BOOLEAN fOnDoctorAssignmentCheck ); diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index dd608752..e25f3e92 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -1616,6 +1616,8 @@ fprintf(NetDebugFile,"\tNPC %d, dtctLvl %d, marking mine at gridno %d, gridCost void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) { +#ifdef ENABLE_ZOMBIES + // added by Flugente: static pointers, used to break out of an endless circles (currently only used for zombie AI) static SOLDIERTYPE* pLastDecisionSoldier = NULL; static INT16 lastdecisioncount = 0; @@ -1642,6 +1644,7 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) pLastDecisionSoldier = pSoldier; lastdecisioncount = 0; } +#endif /* if (Status.gamePaused) @@ -1930,11 +1933,15 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) { if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV)) { +#ifdef ENABLE_ZOMBIES if ( pSoldier->IsZombie() ) { pSoldier->aiData.bAction = ZombieDecideAction(pSoldier); } else if (CREATURE_OR_BLOODCAT( pSoldier )) +#else + if (CREATURE_OR_BLOODCAT( pSoldier )) +#endif { pSoldier->aiData.bAction = CreatureDecideAction( pSoldier ); } @@ -2000,7 +2007,9 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) { // turn based... abort this guy's turn EndAIGuysTurn( pSoldier ); +#ifdef ENABLE_ZOMBIES lastdecisioncount = 0; +#endif } } else @@ -2281,8 +2290,10 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier) StartEnemyTaunt( pSoldier, TAUNT_SEEK_NOISE ); else if (pSoldier->aiData.bAction == AI_ACTION_RUN_AWAY ) StartEnemyTaunt( pSoldier, TAUNT_RUN_AWAY ); +#ifdef ENABLE_ZOMBIES else if ( pSoldier->IsZombie() ) // Madd: Zombies randomly moan... pSoldier->DoMercBattleSound( (INT8)( BATTLE_SOUND_LAUGH1 ) ); +#endif } } case AI_ACTION_APPROACH_MERC: // walk up to someone to talk diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index a3cab901..a4f7a280 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -1296,98 +1296,100 @@ INT32 ClosestSeenOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLeve // special variant with a minor twist for the zombie AI -INT32 ClosestSeenOpponentforZombie(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel) -{ - INT32 sGridNo, sClosestOpponent = NOWHERE; - UINT32 uiLoop; - INT32 iRange, iClosestRange = 1500; - INT8 *pbPersOL; - INT8 bLevel, bClosestLevel; - SOLDIERTYPE * pOpp; - - bClosestLevel = -1; - - // look through this man's personal & public opplists for opponents known - for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) +#ifdef ENABLE_ZOMBIES + INT32 ClosestSeenOpponentforZombie(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel) { - pOpp = MercSlots[ uiLoop ]; + INT32 sGridNo, sClosestOpponent = NOWHERE; + UINT32 uiLoop; + INT32 iRange, iClosestRange = 1500; + INT8 *pbPersOL; + INT8 bLevel, bClosestLevel; + SOLDIERTYPE * pOpp; - // if this merc is inactive, at base, on assignment, or dead - if (!pOpp) + bClosestLevel = -1; + + // look through this man's personal & public opplists for opponents known + for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { - continue; // next merc + pOpp = MercSlots[ uiLoop ]; + + // if this merc is inactive, at base, on assignment, or dead + if (!pOpp) + { + continue; // next merc + } + + // if this merc is neutral/on same side, he's not an opponent + if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide)) + { + continue; // next merc + } + + // Special stuff for Carmen the bounty hunter + if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpp->ubProfile != 64) + { + continue; // next opponent + } + + pbPersOL = pSoldier->aiData.bOppList + pOpp->ubID; + + // if this opponent is not seen personally + if (*pbPersOL != SEEN_CURRENTLY) + { + continue; // next merc + } + + // since we're dealing with seen people, use exact gridnos + sGridNo = pOpp->sGridNo; + bLevel = pOpp->pathing.bLevel; + + // if we are standing at that gridno(!, obviously our info is old...) + if (sGridNo == pSoldier->sGridNo) + { + continue; // next merc + } + + // special: allow zombies to also find opponents on a roof + // otherwise they have will never decide to climb a roof while they are seeing an enemy + // this function is used only for turning towards closest opponent or changing stance + // as such, if they AI is in a building, + // we should ignore people who are on the roof of the same building as the AI + if ( !pSoldier->IsZombie() && (bLevel != pSoldier->pathing.bLevel) && SameBuilding( pSoldier->sGridNo, sGridNo ) ) + { + continue; + } + + // I hope this will be good enough; otherwise we need a fractional/world-units-based 2D distance function + //sRange = PythSpacesAway( pSoldier->sGridNo, sGridNo); + iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ); + + if (iRange < iClosestRange) + { + iClosestRange = iRange; + sClosestOpponent = sGridNo; + bClosestLevel = bLevel; + } } - // if this merc is neutral/on same side, he's not an opponent - if ( CONSIDERED_NEUTRAL( pSoldier, pOpp ) || (pSoldier->bSide == pOpp->bSide)) + #ifdef DEBUGDECISIONS + if (!TileIsOutOfBounds(sClosestOpponent)) { - continue; // next merc + AINumMessage("CLOSEST OPPONENT is at gridno ",sClosestOpponent); } + #endif - // Special stuff for Carmen the bounty hunter - if (pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpp->ubProfile != 64) + if (psGridNo) { - continue; // next opponent + *psGridNo = sClosestOpponent; } - - pbPersOL = pSoldier->aiData.bOppList + pOpp->ubID; - - // if this opponent is not seen personally - if (*pbPersOL != SEEN_CURRENTLY) + if (pbLevel) { - continue; // next merc + *pbLevel = bClosestLevel; } - - // since we're dealing with seen people, use exact gridnos - sGridNo = pOpp->sGridNo; - bLevel = pOpp->pathing.bLevel; - - // if we are standing at that gridno(!, obviously our info is old...) - if (sGridNo == pSoldier->sGridNo) - { - continue; // next merc - } - - // special: allow zombies to also find opponents on a roof - // otherwise they have will never decide to climb a roof while they are seeing an enemy - // this function is used only for turning towards closest opponent or changing stance - // as such, if they AI is in a building, - // we should ignore people who are on the roof of the same building as the AI - if ( !pSoldier->IsZombie() && (bLevel != pSoldier->pathing.bLevel) && SameBuilding( pSoldier->sGridNo, sGridNo ) ) - { - continue; - } - - // I hope this will be good enough; otherwise we need a fractional/world-units-based 2D distance function - //sRange = PythSpacesAway( pSoldier->sGridNo, sGridNo); - iRange = GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ); - - if (iRange < iClosestRange) - { - iClosestRange = iRange; - sClosestOpponent = sGridNo; - bClosestLevel = bLevel; - } - } - -#ifdef DEBUGDECISIONS - if (!TileIsOutOfBounds(sClosestOpponent)) - { - AINumMessage("CLOSEST OPPONENT is at gridno ",sClosestOpponent); + return( sClosestOpponent ); } #endif - if (psGridNo) - { - *psGridNo = sClosestOpponent; - } - if (pbLevel) - { - *pbLevel = bClosestLevel; - } - return( sClosestOpponent ); -} - INT32 ClosestPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ) { @@ -2581,9 +2583,11 @@ UINT8 SoldierDifficultyLevel( SOLDIERTYPE * pSoldier ) bDifficulty = 4; break; +#ifdef ENABLE_ZOMBIES case SOLDIER_CLASS_ZOMBIE: bDifficulty = bDifficultyBase; break; +#endif default: if (pSoldier->bTeam == CREATURE_TEAM) @@ -2733,7 +2737,7 @@ void AINameMessage(SOLDIERTYPE * pSoldier,const STR8 str,INT32 num) // friendlies. The idea is to return a value called "TacticalSituation" which can tell a combatant // whether he should try to undertake a smarter course of action. ///////////////////////////////////////////////////////////////////////////////////////////////// - +/* INT16 AssessTacticalSituation( INT8 bTeam ) { UINT16 ubFriendlyTeamTacticalValue = 0; @@ -2815,7 +2819,7 @@ INT16 AssessTacticalSituation( INT8 bTeam ) } - +*/ // HEADROCK: Function to check whether a team can see the specified soldier. BOOLEAN TeamSeesOpponent( INT8 bTeam, SOLDIERTYPE * pOpponent ) diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 85f8bd15..963562a1 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -64,10 +64,12 @@ UINT32 guiRedSeekCounter = 0, guiRedHelpCounter = 0; guiRedHideCounter = 0; #define MIN_FLANK_DIST_RED 10 * STRAIGHT_RATIO #define MAX_FLANK_DIST_RED 40 * STRAIGHT_RATIO -INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier); -INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier); -INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK); -INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier); +#ifdef ENABLE_ZOMBIES + INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier); + INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier); + INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK); + INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier); +#endif void DoneScheduleAction( SOLDIERTYPE * pSoldier ) { @@ -703,9 +705,11 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) STR16 tempstr; #endif +#ifdef ENABLE_ZOMBIES // Flugente: to prevent an accidental call if ( pSoldier->IsZombie() ) return( ZombieDecideActionGreen(pSoldier) ); +#endif DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders)); @@ -1397,9 +1401,12 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) #ifdef DEBUGDECISIONS STR16 tempstr; #endif + +#ifdef ENABLE_ZOMBIES // Flugente: to prevent an accidental call if ( pSoldier->IsZombie() ) return( ZombieDecideActionYellow(pSoldier) ); +#endif if (fCivilian) { @@ -2103,10 +2110,12 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || (pSoldier->aiData.bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); - + +#ifdef ENABLE_ZOMBIES // Flugente: to prevent an accidental call if ( pSoldier->IsZombie() ) return( ZombieDecideActionRed(pSoldier, ubUnconsciousOK) ); +#endif // WANNE: Headrock informed me that I should remove that because it needs a lot of CPU! // HEADROCK HAM B2.7: Calculate the overall tactical situation @@ -3827,9 +3836,11 @@ INT16 ubMinAPCost; #endif DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); +#ifdef ENABLE_ZOMBIES // Flugente: to prevent an accidental call if ( pSoldier->IsZombie() ) return( ZombieDecideActionBlack(pSoldier) ); +#endif ATTACKTYPE BestShot = {},BestThrow = {},BestStab = {},BestAttack = {}; BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); @@ -5932,713 +5943,559 @@ void DecideAlertStatus( SOLDIERTYPE *pSoldier ) -// ------------------------------ ZOMBIE AI -------------------------- -INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier) -{ - DOUBLE iChance, iSneaky = 10; - INT8 bInWater; -#ifdef DEBUGDECISIONS - STR16 tempstr; -#endif - - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders)); - - gubNPCPathCount = 0; - - if ( TANK( pSoldier ) ) +#ifdef ENABLE_ZOMBIES + // ------------------------------ ZOMBIE AI -------------------------- + INT8 ZombieDecideActionGreen(SOLDIERTYPE *pSoldier) { - return( AI_ACTION_NONE ); - } + DOUBLE iChance, iSneaky = 10; + INT8 bInWater; + #ifdef DEBUGDECISIONS + STR16 tempstr; + #endif - bInWater = Water( pSoldier->sGridNo ); + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders)); - if ( bInWater && PreRandom( 2 ) ) - { - // don't do nuttin! - return( AI_ACTION_NONE ); - } - - // if real-time, and not in the way, do nothing 90% of the time (for GUARDS!) - // unless in water (could've started there), then we better swim to shore! - - //////////////////////////////////////////////////////////////////////////// - // POINT PATROL: move towards next point unless getting a bit winded - //////////////////////////////////////////////////////////////////////////// - - // this takes priority over water/gas checks, so that point patrol WILL work - // from island to island, and through gas covered areas, too - if ((pSoldier->aiData.bOrders == POINTPATROL) && (pSoldier->bBreath >= 25)) - { - if (PointPatrolAI(pSoldier)) - { - if (!gfTurnBasedAI) - { - // wait after this... - pSoldier->aiData.bNextAction = AI_ACTION_WAIT; - pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); - } - return(AI_ACTION_POINT_PATROL); - } - else - { - // Reset path count to avoid dedlok - gubNPCPathCount = 0; - } - } - - if ((pSoldier->aiData.bOrders == RNDPTPATROL) && (pSoldier->bBreath >=25)) - { - if (RandomPointPatrolAI(pSoldier)) - { - if (!gfTurnBasedAI) - { - // wait after this... - pSoldier->aiData.bNextAction = AI_ACTION_WAIT; - pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); - } - return(AI_ACTION_POINT_PATROL); - } - else - { - // Reset path count to avoid dedlok - gubNPCPathCount = 0; - } - } - - //////////////////////////////////////////////////////////////////////////// - // WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND - //////////////////////////////////////////////////////////////////////////// - - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: get out of water and gas")); - - if ( bInWater ) - { - pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - - return(AI_ACTION_LEAVE_WATER_GAS); - } - } - - //////////////////////////////////////////////////////////////////////// - // REST IF RUNNING OUT OF BREATH - //////////////////////////////////////////////////////////////////////// - - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: rest if running out of breath")); - // if our breath is running a bit low, and we're not in the way or in water - if ((pSoldier->bBreath < 25) && !bInWater) - { - // take a breather for gods sake! - // for realtime, AI will use a standard wait set outside of here - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); - } - - - //////////////////////////////////////////////////////////////////////////// - // CLIMB A BUILDING - //////////////////////////////////////////////////////////////////////////// - - if ( gGameExternalOptions.fZombieCanClimb && pSoldier->aiData.bLastAction != AI_ACTION_CLIMB_ROOF && !is_networked ) - { - iChance = 10 + pSoldier->aiData.bBypassToGreen; - - // set base chance and maximum seeking distance according to orders - switch (pSoldier->aiData.bOrders) - { - case STATIONARY: iChance *= 0; break; - case ONGUARD: iChance += 10; break; - case ONCALL: break; - case CLOSEPATROL: iChance += -20; break; - case RNDPTPATROL: - case POINTPATROL: iChance = -30; break; - case FARPATROL: iChance += -40; break; - case SEEKENEMY: iChance += -30; break; - case SNIPER: iChance += 70; break; - } - - // modify for attitude - switch (pSoldier->aiData.bAttitude) - { - case DEFENSIVE: iChance *= 1.5; break; - case BRAVESOLO: iChance /= 2; break; - case BRAVEAID: iChance /= 2; break; - case CUNNINGSOLO: iChance *= 1; break; - case CUNNINGAID: iChance /= 1; break; - case AGGRESSIVE: iChance /= 3; break; - case ATTACKSLAYONLY: break; - } - - // This is the chance that we want to be on the roof. If already there, invert the chance to see if we want back - // down - if (pSoldier->pathing.bLevel > 0) - { - iChance = 100 - iChance; - } - - if ((INT16) PreRandom(100) < iChance) - { - BOOLEAN fUp = FALSE; - if ( pSoldier->pathing.bLevel == 0 ) - { - fUp = TRUE; - } - else if (pSoldier->pathing.bLevel > 0 ) - { - fUp = FALSE; - } - - if ( CanClimbFromHere ( pSoldier, fUp ) ) - { - DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Zombie %d is climbing roof",pSoldier->ubID) ); - return( AI_ACTION_CLIMB_ROOF ); - } - else - { - pSoldier->aiData.usActionData = FindClosestClimbPoint(pSoldier, fUp ); - // Added the check here because sniper militia who are locked inside of a building without keys - // will still have a >100% chance to want to climb, which means an infinite loop. In fact, any - // time a move is desired, there probably also will be a need to check for a path. - if ( !TileIsOutOfBounds(pSoldier->aiData.usActionData) && - LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK, 0 )) - { - return( AI_ACTION_MOVE_TO_CLIMB ); - } - } - } - } - - - //////////////////////////////////////////////////////////////////////////// - // RANDOM PATROL: determine % chance to start a new patrol route - //////////////////////////////////////////////////////////////////////////// - if (!gubNPCPathCount) // try to limit pathing in Green AI - { - iChance = 25 + pSoldier->aiData.bBypassToGreen; - - // set base chance according to orders - switch (pSoldier->aiData.bOrders) - { - case STATIONARY: iChance += -20; break; - case ONGUARD: iChance += -15; break; - case ONCALL: break; - case CLOSEPATROL: iChance += +15; break; - case RNDPTPATROL: - case POINTPATROL: iChance = 0; break; - case FARPATROL: iChance += +25; break; - case SEEKENEMY: iChance += -10; break; - case SNIPER: iChance += -10; break; - } - - // modify chance of patrol (and whether it's a sneaky one) by attitude - switch (pSoldier->aiData.bAttitude) - { - case DEFENSIVE: iChance += -10; break; - case BRAVESOLO: iChance += 5; break; - case BRAVEAID: break; - case CUNNINGSOLO: iChance += 5; iSneaky += 10; break; - case CUNNINGAID: iSneaky += 5; break; - case AGGRESSIVE: iChance += 10; iSneaky += -5; break; - case ATTACKSLAYONLY: iChance += 10; iSneaky += -5; break; - } - - - // if we're in water with land miles (> 25 tiles) away, - // OR if we roll under the chance calculated - if (bInWater || ((INT16) PreRandom(100) < iChance)) - { - pSoldier->aiData.usActionData = RandDestWithinRange(pSoldier); + gubNPCPathCount = 0; - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { - pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_RANDOM_PATROL ); - } - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - RANDOM PATROL to grid %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif + if ( TANK( pSoldier ) ) + { + return( AI_ACTION_NONE ); + } + bInWater = Water( pSoldier->sGridNo ); + + if ( bInWater && PreRandom( 2 ) ) + { + // don't do nuttin! + return( AI_ACTION_NONE ); + } + + // if real-time, and not in the way, do nothing 90% of the time (for GUARDS!) + // unless in water (could've started there), then we better swim to shore! + + //////////////////////////////////////////////////////////////////////////// + // POINT PATROL: move towards next point unless getting a bit winded + //////////////////////////////////////////////////////////////////////////// + + // this takes priority over water/gas checks, so that point patrol WILL work + // from island to island, and through gas covered areas, too + if ((pSoldier->aiData.bOrders == POINTPATROL) && (pSoldier->bBreath >= 25)) + { + if (PointPatrolAI(pSoldier)) + { if (!gfTurnBasedAI) { // wait after this... pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); } - return(AI_ACTION_RANDOM_PATROL); + return(AI_ACTION_POINT_PATROL); + } + else + { + // Reset path count to avoid dedlok + gubNPCPathCount = 0; } } - } - if (!gubNPCPathCount) // try to limit pathing in Green AI - { - //////////////////////////////////////////////////////////////////////////// - // SEEK FRIEND: determine %chance for man to pay a friendly visit - //////////////////////////////////////////////////////////////////////////// - - iChance = 25 + pSoldier->aiData.bBypassToGreen; - - // set base chance and maximum seeking distance according to orders - switch (pSoldier->aiData.bOrders) + if ((pSoldier->aiData.bOrders == RNDPTPATROL) && (pSoldier->bBreath >=25)) { - case STATIONARY: iChance += -20; break; - case ONGUARD: iChance += -15; break; - case ONCALL: break; - case CLOSEPATROL: iChance += +10; break; - case RNDPTPATROL: - case POINTPATROL: iChance = -10; break; - case FARPATROL: iChance += +20; break; - case SEEKENEMY: iChance += -10; break; - case SNIPER: iChance += -10; break; - } - - // modify for attitude - switch (pSoldier->aiData.bAttitude) - { - case DEFENSIVE: break; - case BRAVESOLO: iChance /= 2; break; // loners - case BRAVEAID: iChance += 10; break; // friendly - case CUNNINGSOLO: iChance /= 2; break; // loners - case CUNNINGAID: iChance += 10; break; // friendly - case AGGRESSIVE: break; - case ATTACKSLAYONLY: break; - } - - if ((INT16) PreRandom(100) < iChance) - { - if (RandomFriendWithin(pSoldier)) + if (RandomPointPatrolAI(pSoldier)) { - if ( pSoldier->aiData.usActionData == GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_SEEK_FRIEND ) ) + if (!gfTurnBasedAI) { + // wait after this... + pSoldier->aiData.bNextAction = AI_ACTION_WAIT; + pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); + } + return(AI_ACTION_POINT_PATROL); + } + else + { + // Reset path count to avoid dedlok + gubNPCPathCount = 0; + } + } -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - SEEK FRIEND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif + //////////////////////////////////////////////////////////////////////////// + // WHEN LEFT IN WATER OR GAS, GO TO NEAREST REACHABLE SPOT OF UNGASSED LAND + //////////////////////////////////////////////////////////////////////////// - if ( !gfTurnBasedAI) + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: get out of water and gas")); + + if ( bInWater ) + { + pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + + return(AI_ACTION_LEAVE_WATER_GAS); + } + } + + //////////////////////////////////////////////////////////////////////// + // REST IF RUNNING OUT OF BREATH + //////////////////////////////////////////////////////////////////////// + + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: rest if running out of breath")); + // if our breath is running a bit low, and we're not in the way or in water + if ((pSoldier->bBreath < 25) && !bInWater) + { + // take a breather for gods sake! + // for realtime, AI will use a standard wait set outside of here + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + } + + + //////////////////////////////////////////////////////////////////////////// + // CLIMB A BUILDING + //////////////////////////////////////////////////////////////////////////// + + if ( gGameExternalOptions.fZombieCanClimb && pSoldier->aiData.bLastAction != AI_ACTION_CLIMB_ROOF && !is_networked ) + { + iChance = 10 + pSoldier->aiData.bBypassToGreen; + + // set base chance and maximum seeking distance according to orders + switch (pSoldier->aiData.bOrders) + { + case STATIONARY: iChance *= 0; break; + case ONGUARD: iChance += 10; break; + case ONCALL: break; + case CLOSEPATROL: iChance += -20; break; + case RNDPTPATROL: + case POINTPATROL: iChance = -30; break; + case FARPATROL: iChance += -40; break; + case SEEKENEMY: iChance += -30; break; + case SNIPER: iChance += 70; break; + } + + // modify for attitude + switch (pSoldier->aiData.bAttitude) + { + case DEFENSIVE: iChance *= 1.5; break; + case BRAVESOLO: iChance /= 2; break; + case BRAVEAID: iChance /= 2; break; + case CUNNINGSOLO: iChance *= 1; break; + case CUNNINGAID: iChance /= 1; break; + case AGGRESSIVE: iChance /= 3; break; + case ATTACKSLAYONLY: break; + } + + // This is the chance that we want to be on the roof. If already there, invert the chance to see if we want back + // down + if (pSoldier->pathing.bLevel > 0) + { + iChance = 100 - iChance; + } + + if ((INT16) PreRandom(100) < iChance) + { + BOOLEAN fUp = FALSE; + if ( pSoldier->pathing.bLevel == 0 ) + { + fUp = TRUE; + } + else if (pSoldier->pathing.bLevel > 0 ) + { + fUp = FALSE; + } + + if ( CanClimbFromHere ( pSoldier, fUp ) ) + { + DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Zombie %d is climbing roof",pSoldier->ubID) ); + return( AI_ACTION_CLIMB_ROOF ); + } + else + { + pSoldier->aiData.usActionData = FindClosestClimbPoint(pSoldier, fUp ); + // Added the check here because sniper militia who are locked inside of a building without keys + // will still have a >100% chance to want to climb, which means an infinite loop. In fact, any + // time a move is desired, there probably also will be a need to check for a path. + if ( !TileIsOutOfBounds(pSoldier->aiData.usActionData) && + LegalNPCDestination(pSoldier,pSoldier->aiData.usActionData,ENSURE_PATH,WATEROK, 0 )) { - // pause at the end of the walk! - pSoldier->aiData.bNextAction = AI_ACTION_WAIT; - pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY; + return( AI_ACTION_MOVE_TO_CLIMB ); } - - return(AI_ACTION_SEEK_FRIEND); } } } - } - - //////////////////////////////////////////////////////////////////////////// - // LOOK AROUND: determine %chance for man to turn in place - //////////////////////////////////////////////////////////////////////////// - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: Soldier deciding to turn")); - if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) - { - // avoid 2 consecutive random turns in a row - if (pSoldier->aiData.bLastAction != AI_ACTION_CHANGE_FACING) + //////////////////////////////////////////////////////////////////////////// + // RANDOM PATROL: determine % chance to start a new patrol route + //////////////////////////////////////////////////////////////////////////// + if (!gubNPCPathCount) // try to limit pathing in Green AI { iChance = 25 + pSoldier->aiData.bBypassToGreen; // set base chance according to orders - if (pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == SNIPER) - iChance += 25; - - if (pSoldier->aiData.bOrders == ONGUARD) - iChance += 20; - - if (pSoldier->aiData.bAttitude == DEFENSIVE) - iChance += 25; - - if ( pSoldier->aiData.bOrders == SNIPER && pSoldier->pathing.bLevel == 1) - iChance += 35; - - if ((INT16)PreRandom(100) < iChance) + switch (pSoldier->aiData.bOrders) { - // roll random directions (stored in actionData) until different from current - do + case STATIONARY: iChance += -20; break; + case ONGUARD: iChance += -15; break; + case ONCALL: break; + case CLOSEPATROL: iChance += +15; break; + case RNDPTPATROL: + case POINTPATROL: iChance = 0; break; + case FARPATROL: iChance += +25; break; + case SEEKENEMY: iChance += -10; break; + case SNIPER: iChance += -10; break; + } + + // modify chance of patrol (and whether it's a sneaky one) by attitude + switch (pSoldier->aiData.bAttitude) + { + case DEFENSIVE: iChance += -10; break; + case BRAVESOLO: iChance += 5; break; + case BRAVEAID: break; + case CUNNINGSOLO: iChance += 5; iSneaky += 10; break; + case CUNNINGAID: iSneaky += 5; break; + case AGGRESSIVE: iChance += 10; iSneaky += -5; break; + case ATTACKSLAYONLY: iChance += 10; iSneaky += -5; break; + } + + + // if we're in water with land miles (> 25 tiles) away, + // OR if we roll under the chance calculated + if (bInWater || ((INT16) PreRandom(100) < iChance)) + { + pSoldier->aiData.usActionData = RandDestWithinRange(pSoldier); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { - // if man has a LEGAL dominant facing, and isn't facing it, he will turn - // back towards that facing 50% of the time here (normally just enemies) - if ((pSoldier->aiData.bDominantDir >= 0) && (pSoldier->aiData.bDominantDir <= 8) && - (pSoldier->ubDirection != pSoldier->aiData.bDominantDir) && PreRandom(2) && pSoldier->aiData.bOrders != SNIPER ) - { - pSoldier->aiData.usActionData = pSoldier->aiData.bDominantDir; - } - else - { - INT32 iNoiseValue; - BOOLEAN fClimb; - BOOLEAN fReachable; - INT32 sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); - UINT8 ubNoiseDir; - - if (TileIsOutOfBounds(sNoiseGridNo) || - (ubNoiseDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sNoiseGridNo),CenterY(sNoiseGridNo)) - ) == pSoldier->ubDirection ) - - { - pSoldier->aiData.usActionData = PreRandom(8); - } - else - { - pSoldier->aiData.usActionData = ubNoiseDir; - } - } - } while (pSoldier->aiData.usActionData == pSoldier->ubDirection); - - -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - TURNS to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Trying to turn - checking stance validity, sniper = %d",pSoldier->sniper)); - if ( pSoldier->InternalIsValidStance( (INT8) pSoldier->aiData.usActionData, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_RANDOM_PATROL ); + } + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - RANDOM PATROL to grid %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif - if ( !gfTurnBasedAI ) + if (!gfTurnBasedAI) { // wait after this... pSoldier->aiData.bNextAction = AI_ACTION_WAIT; pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); } + return(AI_ACTION_RANDOM_PATROL); + } + } + } - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Soldier is turning")); + if (!gubNPCPathCount) // try to limit pathing in Green AI + { + //////////////////////////////////////////////////////////////////////////// + // SEEK FRIEND: determine %chance for man to pay a friendly visit + //////////////////////////////////////////////////////////////////////////// + + iChance = 25 + pSoldier->aiData.bBypassToGreen; + + // set base chance and maximum seeking distance according to orders + switch (pSoldier->aiData.bOrders) + { + case STATIONARY: iChance += -20; break; + case ONGUARD: iChance += -15; break; + case ONCALL: break; + case CLOSEPATROL: iChance += +10; break; + case RNDPTPATROL: + case POINTPATROL: iChance = -10; break; + case FARPATROL: iChance += +20; break; + case SEEKENEMY: iChance += -10; break; + case SNIPER: iChance += -10; break; + } + + // modify for attitude + switch (pSoldier->aiData.bAttitude) + { + case DEFENSIVE: break; + case BRAVESOLO: iChance /= 2; break; // loners + case BRAVEAID: iChance += 10; break; // friendly + case CUNNINGSOLO: iChance /= 2; break; // loners + case CUNNINGAID: iChance += 10; break; // friendly + case AGGRESSIVE: break; + case ATTACKSLAYONLY: break; + } + + if ((INT16) PreRandom(100) < iChance) + { + if (RandomFriendWithin(pSoldier)) + { + if ( pSoldier->aiData.usActionData == GoAsFarAsPossibleTowards( pSoldier, pSoldier->aiData.usActionData, AI_ACTION_SEEK_FRIEND ) ) + { + + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - SEEK FRIEND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + + if ( !gfTurnBasedAI) + { + // pause at the end of the walk! + pSoldier->aiData.bNextAction = AI_ACTION_WAIT; + pSoldier->aiData.usNextActionData = (UINT16) REALTIME_CIV_AI_DELAY; + } + + return(AI_ACTION_SEEK_FRIEND); + } + } + } + } + + + //////////////////////////////////////////////////////////////////////////// + // LOOK AROUND: determine %chance for man to turn in place + //////////////////////////////////////////////////////////////////////////// + + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionGreen: Soldier deciding to turn")); + if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) + { + // avoid 2 consecutive random turns in a row + if (pSoldier->aiData.bLastAction != AI_ACTION_CHANGE_FACING) + { + iChance = 25 + pSoldier->aiData.bBypassToGreen; + + // set base chance according to orders + if (pSoldier->aiData.bOrders == STATIONARY || pSoldier->aiData.bOrders == SNIPER) + iChance += 25; + + if (pSoldier->aiData.bOrders == ONGUARD) + iChance += 20; + + if (pSoldier->aiData.bAttitude == DEFENSIVE) + iChance += 25; + + if ( pSoldier->aiData.bOrders == SNIPER && pSoldier->pathing.bLevel == 1) + iChance += 35; + + if ((INT16)PreRandom(100) < iChance) + { + // roll random directions (stored in actionData) until different from current + do + { + // if man has a LEGAL dominant facing, and isn't facing it, he will turn + // back towards that facing 50% of the time here (normally just enemies) + if ((pSoldier->aiData.bDominantDir >= 0) && (pSoldier->aiData.bDominantDir <= 8) && + (pSoldier->ubDirection != pSoldier->aiData.bDominantDir) && PreRandom(2) && pSoldier->aiData.bOrders != SNIPER ) + { + pSoldier->aiData.usActionData = pSoldier->aiData.bDominantDir; + } + else + { + INT32 iNoiseValue; + BOOLEAN fClimb; + BOOLEAN fReachable; + INT32 sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); + UINT8 ubNoiseDir; + + if (TileIsOutOfBounds(sNoiseGridNo) || + (ubNoiseDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sNoiseGridNo),CenterY(sNoiseGridNo)) + ) == pSoldier->ubDirection ) + + { + pSoldier->aiData.usActionData = PreRandom(8); + } + else + { + pSoldier->aiData.usActionData = ubNoiseDir; + } + } + } while (pSoldier->aiData.usActionData == pSoldier->ubDirection); + + + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - TURNS to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Trying to turn - checking stance validity, sniper = %d",pSoldier->sniper)); + if ( pSoldier->InternalIsValidStance( (INT8) pSoldier->aiData.usActionData, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + { + + if ( !gfTurnBasedAI ) + { + // wait after this... + pSoldier->aiData.bNextAction = AI_ACTION_WAIT; + pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); + } + + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen: Soldier is turning")); + return(AI_ACTION_CHANGE_FACING); + } + } + } + } + + + //////////////////////////////////////////////////////////////////////////// + // NONE: + //////////////////////////////////////////////////////////////////////////// + + // by default, if everything else fails, just stands in place without turning + // for realtime, regular AI guys will use a standard wait set outside of here + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + } + + INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) + { + UINT8 ubNoiseDir; + INT32 sNoiseGridNo; + INT32 iNoiseValue; + INT32 iChance; + BOOLEAN fClimb; + BOOLEAN fReachable; + #ifdef DEBUGDECISIONS + STR16 tempstr; + #endif + + // determine the most important noise heard, and its relative value + sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); + + if (TileIsOutOfBounds(sNoiseGridNo)) + { + // then we have no business being under YELLOW status any more! + #ifdef RECORDNET + fprintf(NetDebugFile,"\nDecideActionYellow: ERROR - No important noise known by guynum %d\n\n",pSoldier->ubID); + #endif + + #ifdef BETAVERSION + NumMessage("DecideActionYellow: ERROR - No important noise known by guynum ",pSoldier->ubID); + #endif + + return(AI_ACTION_NONE); + } + + //////////////////////////////////////////////////////////////////////////// + // LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise + //////////////////////////////////////////////////////////////////////////// + + // determine direction from this soldier in which the noise lies + ubNoiseDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sNoiseGridNo),CenterY(sNoiseGridNo)); + + // if soldier is not already facing in that direction, + // and the noise source is close enough that it could possibly be seen + if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints ) + { + if ((pSoldier->ubDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= pSoldier->GetMaxDistanceVisible(sNoiseGridNo) ) + { + // set base chance according to orders + if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) ) + iChance = 50; + else // all other orders + iChance = 25; + + if (pSoldier->aiData.bAttitude == DEFENSIVE) + iChance += 15; + + + if ((INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubNoiseDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + { + pSoldier->aiData.usActionData = ubNoiseDir; + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - TURNS TOWARDS NOISE to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif return(AI_ACTION_CHANGE_FACING); } } } - } - - - //////////////////////////////////////////////////////////////////////////// - // NONE: - //////////////////////////////////////////////////////////////////////////// - - // by default, if everything else fails, just stands in place without turning - // for realtime, regular AI guys will use a standard wait set outside of here - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); -} - -INT8 ZombieDecideActionYellow(SOLDIERTYPE *pSoldier) -{ - UINT8 ubNoiseDir; - INT32 sNoiseGridNo; - INT32 iNoiseValue; - INT32 iChance; - BOOLEAN fClimb; - BOOLEAN fReachable; -#ifdef DEBUGDECISIONS - STR16 tempstr; -#endif - - // determine the most important noise heard, and its relative value - sNoiseGridNo = MostImportantNoiseHeard(pSoldier,&iNoiseValue, &fClimb, &fReachable); - - if (TileIsOutOfBounds(sNoiseGridNo)) - { - // then we have no business being under YELLOW status any more! -#ifdef RECORDNET - fprintf(NetDebugFile,"\nDecideActionYellow: ERROR - No important noise known by guynum %d\n\n",pSoldier->ubID); -#endif - -#ifdef BETAVERSION - NumMessage("DecideActionYellow: ERROR - No important noise known by guynum ",pSoldier->ubID); -#endif - - return(AI_ACTION_NONE); - } - - //////////////////////////////////////////////////////////////////////////// - // LOOK AROUND TOWARD NOISE: determine %chance for man to turn towards noise - //////////////////////////////////////////////////////////////////////////// - - // determine direction from this soldier in which the noise lies - ubNoiseDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sNoiseGridNo),CenterY(sNoiseGridNo)); - - // if soldier is not already facing in that direction, - // and the noise source is close enough that it could possibly be seen - if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints ) - { - if ((pSoldier->ubDirection != ubNoiseDir) && PythSpacesAway(pSoldier->sGridNo,sNoiseGridNo) <= pSoldier->GetMaxDistanceVisible(sNoiseGridNo) ) - { - // set base chance according to orders - if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) ) - iChance = 50; - else // all other orders - iChance = 25; - - if (pSoldier->aiData.bAttitude == DEFENSIVE) - iChance += 15; - - - if ((INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubNoiseDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) - { - pSoldier->aiData.usActionData = ubNoiseDir; -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - TURNS TOWARDS NOISE to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - return(AI_ACTION_CHANGE_FACING); - } - } - } - - //////////////////////////////////////////////////////////////////////////// - // RADIO YELLOW ALERT: determine %chance to call others and report noise - //////////////////////////////////////////////////////////////////////////// - - // if we have the action points remaining to RADIO - // (we never want NPCs to choose to radio if they would have to wait a turn) - if ( (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (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 - - // if I actually know something they don't and I ain't swimming (deep water) - if (iChance && !DeepWater( pSoldier->sGridNo )) - { - // CJC: this addition allows for varying difficulty levels for soldier types - iChance += gbDiff[ DIFF_RADIO_RED_ALERT ][ SoldierDifficultyLevel( pSoldier ) ] / 2; - - // Alex: this addition replaces the sectorValue/2 in original JA - //iChance += gsDiff[DIFF_RADIO_RED_ALERT][GameOption[ENEMYDIFFICULTY]] / 2; - - // modify base chance according to orders - switch (pSoldier->aiData.bOrders) - { - case STATIONARY: iChance += 20; break; - case ONGUARD: iChance += 15; break; - case ONCALL: iChance += 10; break; - case CLOSEPATROL: break; - case RNDPTPATROL: - case POINTPATROL: break; - case FARPATROL: iChance += -10; break; - case SEEKENEMY: iChance += -20; break; - case SNIPER: iChance += -10; break; //Madd: sniper contacts are supposed to be automatically reported - } - - // modify base chance according to attitude - switch (pSoldier->aiData.bAttitude) - { - case DEFENSIVE: iChance += 20; break; - case BRAVESOLO: iChance += -10; break; - case BRAVEAID: break; - case CUNNINGSOLO:iChance += -5; break; - case CUNNINGAID: break; - case AGGRESSIVE: iChance += -20; break; - case ATTACKSLAYONLY: iChance = 0; break; - } - -#ifdef DEBUGDECISIONS - AINumMessage("Chance to radio yellow alert = ",iChance); -#endif - - if ((INT16)PreRandom(100) < iChance) - { -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"decides to radio a YELLOW alert!",1000); -#endif - - return(AI_ACTION_YELLOW_ALERT); - } - } - } - - if ( TANK( pSoldier ) ) - { - return( AI_ACTION_NONE ); - } - - //////////////////////////////////////////////////////////////////////// - // REST IF RUNNING OUT OF BREATH - //////////////////////////////////////////////////////////////////////// - - // if our breath is running a bit low, and we're not in water - if ((pSoldier->bBreath < 25) && !pSoldier->MercInWater()) - { - // take a breather for gods sake! - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); - } - - //continue flanking - INT32 tempGridNo; - - if (TileIsOutOfBounds(sNoiseGridNo)) - tempGridNo = pSoldier->lastFlankSpot; - else - tempGridNo = sNoiseGridNo; - - if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_YELLOW ) - { - INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); - INT16 origDir = pSoldier->origDir; - pSoldier->numFlanks += 1; - if ( pSoldier->flags.lastFlankLeft ) - { - if ( origDir > currDir ) - origDir -= 8; - - if ( (currDir - origDir) >= 4 ) - { - pSoldier->numFlanks = MAX_FLANKS_YELLOW; - } - else - { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) - return AI_ACTION_FLANK_LEFT ; - else - pSoldier->numFlanks = MAX_FLANKS_YELLOW; - } - } - else - { - if ( origDir < currDir ) - origDir += 8; - - if ( (origDir - currDir) >= 4 ) - { - pSoldier->numFlanks = MAX_FLANKS_YELLOW; - } - else - { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))//&& (origDir - currDir) < 2 ) - return AI_ACTION_FLANK_RIGHT ; - else - pSoldier->numFlanks = MAX_FLANKS_YELLOW; - } - } - } - - if ( pSoldier->numFlanks == MAX_FLANKS_YELLOW ) - { - pSoldier->numFlanks += 1; - pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,tempGridNo,AI_ACTION_SEEK_NOISE); - return AI_ACTION_SEEK_NOISE ; - } - - //////////////////////////////////////////////////////////////////////////// - // SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong - //////////////////////////////////////////////////////////////////////////// - if ((INT16)PreRandom(100) < 50 ) - { -#ifdef RECORDNET - fprintf(NetDebugFile,"\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n",pSoldier->ubID); -#endif - -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"ignores noise completely and BYPASSES to GREEN!",1000); -#endif - - // Skip YELLOW until new situation, 15% extra chance to do GREEN actions - pSoldier->aiData.bBypassToGreen = 15; - return(ZombieDecideActionGreen(pSoldier)); - } - - //////////////////////////////////////////////////////////////////////////// - // DO NOTHING: Not enough points left to move, so save them for next turn - //////////////////////////////////////////////////////////////////////////// - -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"- DOES NOTHING (YELLOW)",1000); -#endif - - // by default, if everything else fails, just stands in place without turning - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); -} - - -INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) -{ - INT32 iDummy; - INT32 iChance,sClosestOpponent,sClosestFriend; - INT32 sClosestDisturbance, sDistVisible; - UINT8 ubCanMove,ubOpponentDir; - INT8 bInWater, bInDeepWater; - INT8 bSeekPts = 0, bHelpPts = 0, bWatchPts = 0; - INT8 bHighestWatchLoc; -#ifdef AI_TIMING_TEST - UINT32 uiStartTime, uiEndTime; -#endif -#ifdef DEBUGDECISIONS - STR16 tempstr; -#endif - BOOLEAN fClimb; - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionRed: soldier orders = %d",pSoldier->aiData.bOrders)); - - // if we have absolutely no action points, we can't do a thing under RED! - if (!pSoldier->bActionPoints) - { - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); - } - - // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) - ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); - - - // determine if we happen to be in water (in which case we're in BIG trouble!) - bInWater = Water( pSoldier->sGridNo ); - bInDeepWater = Water( pSoldier->sGridNo ); - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch and rest if running out of breath"); - //////////////////////////////////////////////////////////////////////// - // CROUCH & REST IF RUNNING OUT OF BREATH - //////////////////////////////////////////////////////////////////////// - - // if our breath is running a bit low, and we're not in water or under fire - if ((pSoldier->bBreath < 25) && !bInWater && !pSoldier->aiData.bUnderFire) - { - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); - } - - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); - //////////////////////////////////////////////////////////////////////////// - // RADIO RED ALERT: determine %chance to call others and report contact - //////////////////////////////////////////////////////////////////////////// - - if ( !TANK( pSoldier ) ) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai"); //////////////////////////////////////////////////////////////////////////// - // MAIN RED AI: Decide soldier's preference between SEEKING,HELPING & HIDING + // RADIO YELLOW ALERT: determine %chance to call others and report noise //////////////////////////////////////////////////////////////////////////// - // get the location of the closest reachable opponent - sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb); + // if we have the action points remaining to RADIO + // (we never want NPCs to choose to radio if they would have to wait a turn) + if ( (pSoldier->bActionPoints >= APBPConstants[AP_RADIO]) && (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 - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking"); - // continue flanking + // if I actually know something they don't and I ain't swimming (deep water) + if (iChance && !DeepWater( pSoldier->sGridNo )) + { + // CJC: this addition allows for varying difficulty levels for soldier types + iChance += gbDiff[ DIFF_RADIO_RED_ALERT ][ SoldierDifficultyLevel( pSoldier ) ] / 2; + + // Alex: this addition replaces the sectorValue/2 in original JA + //iChance += gsDiff[DIFF_RADIO_RED_ALERT][GameOption[ENEMYDIFFICULTY]] / 2; + + // modify base chance according to orders + switch (pSoldier->aiData.bOrders) + { + case STATIONARY: iChance += 20; break; + case ONGUARD: iChance += 15; break; + case ONCALL: iChance += 10; break; + case CLOSEPATROL: break; + case RNDPTPATROL: + case POINTPATROL: break; + case FARPATROL: iChance += -10; break; + case SEEKENEMY: iChance += -20; break; + case SNIPER: iChance += -10; break; //Madd: sniper contacts are supposed to be automatically reported + } + + // modify base chance according to attitude + switch (pSoldier->aiData.bAttitude) + { + case DEFENSIVE: iChance += 20; break; + case BRAVESOLO: iChance += -10; break; + case BRAVEAID: break; + case CUNNINGSOLO:iChance += -5; break; + case CUNNINGAID: break; + case AGGRESSIVE: iChance += -20; break; + case ATTACKSLAYONLY: iChance = 0; break; + } + + #ifdef DEBUGDECISIONS + AINumMessage("Chance to radio yellow alert = ",iChance); + #endif + + if ((INT16)PreRandom(100) < iChance) + { + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"decides to radio a YELLOW alert!",1000); + #endif + + return(AI_ACTION_YELLOW_ALERT); + } + } + } + + if ( TANK( pSoldier ) ) + { + return( AI_ACTION_NONE ); + } + + //////////////////////////////////////////////////////////////////////// + // REST IF RUNNING OUT OF BREATH + //////////////////////////////////////////////////////////////////////// + + // if our breath is running a bit low, and we're not in water + if ((pSoldier->bBreath < 25) && !pSoldier->MercInWater()) + { + // take a breather for gods sake! + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + } + + //continue flanking INT32 tempGridNo; - if (TileIsOutOfBounds(sClosestDisturbance)) + if (TileIsOutOfBounds(sNoiseGridNo)) tempGridNo = pSoldier->lastFlankSpot; else - tempGridNo = sClosestDisturbance; + tempGridNo = sNoiseGridNo; - if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_RED && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) + if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_YELLOW ) { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: continue flanking"); INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); INT16 origDir = pSoldier->origDir; pSoldier->numFlanks += 1; @@ -6649,16 +6506,15 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( (currDir - origDir) >= 4 ) { - pSoldier->numFlanks = MAX_FLANKS_RED; + pSoldier->numFlanks = MAX_FLANKS_YELLOW; } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); - + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) return AI_ACTION_FLANK_LEFT ; else - pSoldier->numFlanks = MAX_FLANKS_RED; + pSoldier->numFlanks = MAX_FLANKS_YELLOW; } } else @@ -6668,226 +6524,420 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) if ( (origDir - currDir) >= 4 ) { - pSoldier->numFlanks = MAX_FLANKS_RED; + pSoldier->numFlanks = MAX_FLANKS_YELLOW; } else { - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) )//&& (origDir - currDir) < 2 ) + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData))//&& (origDir - currDir) < 2 ) return AI_ACTION_FLANK_RIGHT ; else - pSoldier->numFlanks = MAX_FLANKS_RED; + pSoldier->numFlanks = MAX_FLANKS_YELLOW; } } } - if ( pSoldier->numFlanks == MAX_FLANKS_RED ) + + if ( pSoldier->numFlanks == MAX_FLANKS_YELLOW ) { pSoldier->numFlanks += 1; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking"); - if (PythSpacesAway ( pSoldier->sGridNo, tempGridNo ) > MIN_FLANK_DIST_RED * 2 ) - { - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,tempGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); + pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,tempGridNo,AI_ACTION_SEEK_NOISE); + return AI_ACTION_SEEK_NOISE ; + } + + //////////////////////////////////////////////////////////////////////////// + // SWITCH TO GREEN: determine if soldier acts as if nothing at all was wrong + //////////////////////////////////////////////////////////////////////////// + if ((INT16)PreRandom(100) < 50 ) + { + #ifdef RECORDNET + fprintf(NetDebugFile,"\tDecideActionYellow: guynum %d ignores noise, switching to GREEN AI...\n",pSoldier->ubID); + #endif - if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) ) + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"ignores noise completely and BYPASSES to GREEN!",1000); + #endif + + // Skip YELLOW until new situation, 15% extra chance to do GREEN actions + pSoldier->aiData.bBypassToGreen = 15; + return(ZombieDecideActionGreen(pSoldier)); + } + + //////////////////////////////////////////////////////////////////////////// + // DO NOTHING: Not enough points left to move, so save them for next turn + //////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"- DOES NOTHING (YELLOW)",1000); + #endif + + // by default, if everything else fails, just stands in place without turning + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + } + + + INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) + { + INT32 iDummy; + INT32 iChance,sClosestOpponent,sClosestFriend; + INT32 sClosestDisturbance, sDistVisible; + UINT8 ubCanMove,ubOpponentDir; + INT8 bInWater, bInDeepWater; + INT8 bSeekPts = 0, bHelpPts = 0, bWatchPts = 0; + INT8 bHighestWatchLoc; + #ifdef AI_TIMING_TEST + UINT32 uiStartTime, uiEndTime; + #endif + #ifdef DEBUGDECISIONS + STR16 tempstr; + #endif + BOOLEAN fClimb; + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("ZombieDecideActionRed: soldier orders = %d",pSoldier->aiData.bOrders)); + + // if we have absolutely no action points, we can't do a thing under RED! + if (!pSoldier->bActionPoints) + { + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + } + + // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) + ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); + + + // determine if we happen to be in water (in which case we're in BIG trouble!) + bInWater = Water( pSoldier->sGridNo ); + bInDeepWater = Water( pSoldier->sGridNo ); + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: crouch and rest if running out of breath"); + //////////////////////////////////////////////////////////////////////// + // CROUCH & REST IF RUNNING OUT OF BREATH + //////////////////////////////////////////////////////////////////////// + + // if our breath is running a bit low, and we're not in water or under fire + if ((pSoldier->bBreath < 25) && !bInWater && !pSoldier->aiData.bUnderFire) + { + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + } + + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: radio red alert?"); + //////////////////////////////////////////////////////////////////////////// + // RADIO RED ALERT: determine %chance to call others and report contact + //////////////////////////////////////////////////////////////////////////// + + if ( !TANK( pSoldier ) ) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: main red ai"); + + //////////////////////////////////////////////////////////////////////////// + // MAIN RED AI: Decide soldier's preference between SEEKING,HELPING & HIDING + //////////////////////////////////////////////////////////////////////////// + + // get the location of the closest reachable opponent + sClosestDisturbance = ClosestReachableDisturbance(pSoldier,ubUnconsciousOK, &fClimb); + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: check to continue flanking"); + // continue flanking + INT32 tempGridNo; + + if (TileIsOutOfBounds(sClosestDisturbance)) + tempGridNo = pSoldier->lastFlankSpot; + else + tempGridNo = sClosestDisturbance; + + if ( pSoldier->numFlanks > 0 && pSoldier->numFlanks < MAX_FLANKS_RED && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: continue flanking"); + INT16 currDir = GetDirectionFromGridNo ( tempGridNo, pSoldier ); + INT16 origDir = pSoldier->origDir; + pSoldier->numFlanks += 1; + if ( pSoldier->flags.lastFlankLeft ) { - // reserve APs for a possible crouch plus a shot - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, tempGridNo, (INT8) (MinAPsToAttack( pSoldier, tempGridNo, ADDTURNCOST) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + if ( origDir > currDir ) + origDir -= 8; + + if ( (currDir - origDir) >= 4 ) + { + pSoldier->numFlanks = MAX_FLANKS_RED; + } + else + { + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_LEFT); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) ) //&& (currDir - origDir) < 2 ) + return AI_ACTION_FLANK_LEFT ; + else + pSoldier->numFlanks = MAX_FLANKS_RED; + } + } + else + { + if ( origDir < currDir ) + origDir += 8; + + if ( (origDir - currDir) >= 4 ) + { + pSoldier->numFlanks = MAX_FLANKS_RED; + } + else + { + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, tempGridNo , AI_ACTION_FLANK_RIGHT); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData) )//&& (origDir - currDir) < 2 ) + return AI_ACTION_FLANK_RIGHT ; + else + pSoldier->numFlanks = MAX_FLANKS_RED; + } + } + } + if ( pSoldier->numFlanks == MAX_FLANKS_RED ) + { + pSoldier->numFlanks += 1; + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: stop flanking"); + if (PythSpacesAway ( pSoldier->sGridNo, tempGridNo ) > MIN_FLANK_DIST_RED * 2 ) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,tempGridNo,GetAPsProne( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); + + if ( LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, tempGridNo, pSoldier->pathing.bLevel, TRUE) ) + { + // reserve APs for a possible crouch plus a shot + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, tempGridNo, (INT8) (MinAPsToAttack( pSoldier, tempGridNo, ADDTURNCOST) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + pSoldier->aiData.fAIFlags |= AI_CAUTIOUS; + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + return(AI_ACTION_SEEK_OPPONENT); + } + } + else { - pSoldier->aiData.fAIFlags |= AI_CAUTIOUS; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; return(AI_ACTION_SEEK_OPPONENT); } } else { - return(AI_ACTION_SEEK_OPPONENT); + pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier,pSoldier->aiData.bAIMorale,&iDummy); + return AI_ACTION_TAKE_COVER ; } } - else - { - pSoldier->aiData.usActionData = FindBestNearbyCover(pSoldier,pSoldier->aiData.bAIMorale,&iDummy); - return AI_ACTION_TAKE_COVER ; - } - } - // if we can move at least 1 square's worth - // and have more APs than we want to reserve - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: can we move? = %d, APs = %d",ubCanMove,pSoldier->bActionPoints)); + // if we can move at least 1 square's worth + // and have more APs than we want to reserve + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: can we move? = %d, APs = %d",ubCanMove,pSoldier->bActionPoints)); - if (ubCanMove && pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED]) - { - // zombies don't hide, they seek prey... - bSeekPts = 99; - bHelpPts = 40; - bWatchPts -= 10; + if (ubCanMove && pSoldier->bActionPoints > APBPConstants[MAX_AP_CARRIED]) + { + // zombies don't hide, they seek prey... + bSeekPts = 99; + bHelpPts = 40; + bWatchPts -= 10; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: checking seek/help/watch points... orders = %d, attitude = %d",pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); - // calculate initial points for watch based on highest watch loc + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: checking seek/help/watch points... orders = %d, attitude = %d",pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); + // calculate initial points for watch based on highest watch loc - bWatchPts = GetHighestWatchedLocPoints( pSoldier->ubID ); - if ( bWatchPts <= 0 ) - { - // no watching - bWatchPts = -99; - } - - // modify RED movement tendencies according to morale - switch (pSoldier->aiData.bAIMorale) - { - case MORALE_HOPELESS: bSeekPts = -99; bHelpPts = -99; bWatchPts = -99; break; - case MORALE_WORRIED: bSeekPts += -2; bHelpPts += 0; bWatchPts += 1; break; - case MORALE_NORMAL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; - case MORALE_CONFIDENT: bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; - case MORALE_FEARLESS: bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; - } - - // modify tendencies according to orders - switch (pSoldier->aiData.bOrders) - { - case STATIONARY: bSeekPts += -1; bHelpPts += -1; bWatchPts += +1; break; - case ONGUARD: bSeekPts += -1; bHelpPts += 0; bWatchPts += +1; break; - case CLOSEPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; - case RNDPTPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; - case POINTPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; - case FARPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; - case ONCALL: bSeekPts += 0; bHelpPts += +1; bWatchPts += 0; break; - case SEEKENEMY: bSeekPts += +1; bHelpPts += 0; bWatchPts += -1; break; - case SNIPER: bSeekPts += -1; bHelpPts += 0; bWatchPts += +1; break; - } - - // modify tendencies according to attitude - switch (pSoldier->aiData.bAttitude) - { - case DEFENSIVE: bSeekPts += -1; bHelpPts += 0; bWatchPts += +1; break; - case BRAVESOLO: bSeekPts += +1; bHelpPts += -1; bWatchPts += -1; break; - case BRAVEAID: bSeekPts += +1; bHelpPts += +1; bWatchPts += -1; break; - case CUNNINGSOLO: bSeekPts += 1; bHelpPts += -1; bWatchPts += 0; break; - case CUNNINGAID: bSeekPts += 1; bHelpPts += +1; bWatchPts += 0; break; - case AGGRESSIVE: bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; - case ATTACKSLAYONLY:bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; - } - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: hide = %d, seek = %d, watch = %d, help = %d",bHidePts,bSeekPts,bWatchPts,bHelpPts)); - // while one of the three main RED REACTIONS remains viable - while ((bSeekPts > -90) || (bHelpPts > -90) ) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to seek"); - // if SEEKING is possible and at least as desirable as helping or hiding - if ( ((bSeekPts > -90) && (bSeekPts >= bHelpPts) && (bSeekPts >= bWatchPts )) ) + bWatchPts = GetHighestWatchedLocPoints( pSoldier->ubID ); + if ( bWatchPts <= 0 ) { -#ifdef AI_TIMING_TESTS - uiStartTime = GetJA2Clock(); -#endif + // no watching + bWatchPts = -99; + } -#ifdef AI_TIMING_TESTS - uiEndTime = GetJA2Clock(); - guiRedSeekTimeTotal += (uiEndTime - uiStartTime); - guiRedSeekCounter++; -#endif - // if there is an opponent reachable - if (!TileIsOutOfBounds(sClosestDisturbance) ) + // modify RED movement tendencies according to morale + switch (pSoldier->aiData.bAIMorale) + { + case MORALE_HOPELESS: bSeekPts = -99; bHelpPts = -99; bWatchPts = -99; break; + case MORALE_WORRIED: bSeekPts += -2; bHelpPts += 0; bWatchPts += 1; break; + case MORALE_NORMAL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; + case MORALE_CONFIDENT: bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; + case MORALE_FEARLESS: bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; + } + + // modify tendencies according to orders + switch (pSoldier->aiData.bOrders) + { + case STATIONARY: bSeekPts += -1; bHelpPts += -1; bWatchPts += +1; break; + case ONGUARD: bSeekPts += -1; bHelpPts += 0; bWatchPts += +1; break; + case CLOSEPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; + case RNDPTPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; + case POINTPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; + case FARPATROL: bSeekPts += 0; bHelpPts += 0; bWatchPts += 0; break; + case ONCALL: bSeekPts += 0; bHelpPts += +1; bWatchPts += 0; break; + case SEEKENEMY: bSeekPts += +1; bHelpPts += 0; bWatchPts += -1; break; + case SNIPER: bSeekPts += -1; bHelpPts += 0; bWatchPts += +1; break; + } + + // modify tendencies according to attitude + switch (pSoldier->aiData.bAttitude) + { + case DEFENSIVE: bSeekPts += -1; bHelpPts += 0; bWatchPts += +1; break; + case BRAVESOLO: bSeekPts += +1; bHelpPts += -1; bWatchPts += -1; break; + case BRAVEAID: bSeekPts += +1; bHelpPts += +1; bWatchPts += -1; break; + case CUNNINGSOLO: bSeekPts += 1; bHelpPts += -1; bWatchPts += 0; break; + case CUNNINGAID: bSeekPts += 1; bHelpPts += +1; bWatchPts += 0; break; + case AGGRESSIVE: bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; + case ATTACKSLAYONLY:bSeekPts += +1; bHelpPts += 0; bWatchPts += 0; break; + } + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("decideactionred: hide = %d, seek = %d, watch = %d, help = %d",bHidePts,bSeekPts,bWatchPts,bHelpPts)); + // while one of the three main RED REACTIONS remains viable + while ((bSeekPts > -90) || (bHelpPts > -90) ) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to seek"); + // if SEEKING is possible and at least as desirable as helping or hiding + if ( ((bSeekPts > -90) && (bSeekPts >= bHelpPts) && (bSeekPts >= bWatchPts )) ) { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: seek opponent"); - ////////////////////////////////////////////////////////////////////// - // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT - ////////////////////////////////////////////////////////////////////// + #ifdef AI_TIMING_TESTS + uiStartTime = GetJA2Clock(); + #endif - // try to move towards him - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,GetAPsCrouch( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); - - // if it's possible - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + #ifdef AI_TIMING_TESTS + uiEndTime = GetJA2Clock(); + guiRedSeekTimeTotal += (uiEndTime - uiStartTime); + guiRedSeekCounter++; + #endif + // if there is an opponent reachable + if (!TileIsOutOfBounds(sClosestDisturbance) ) { -#ifdef DEBUGDECISIONS - // do it! - sprintf(tempstr,"%s - SEEKING OPPONENT at grid %d, MOVING to %d", - pSoldier->name,sClosestDisturbance,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: seek opponent"); + ////////////////////////////////////////////////////////////////////// + // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT + ////////////////////////////////////////////////////////////////////// - if ( fClimb && gGameExternalOptions.fZombieCanClimb )//&& pSoldier->aiData.usActionData == sClosestDisturbance) + // try to move towards him + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,GetAPsCrouch( pSoldier, TRUE),AI_ACTION_SEEK_OPPONENT,0); + + // if it's possible + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { - // need to climb AND have enough APs to get there this turn - BOOLEAN fUp = TRUE; - if (pSoldier->pathing.bLevel > 0 ) - fUp = FALSE; + #ifdef DEBUGDECISIONS + // do it! + sprintf(tempstr,"%s - SEEKING OPPONENT at grid %d, MOVING to %d", + pSoldier->name,sClosestDisturbance,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif - if (!fUp) - DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) ); - - // As mentioned in the next part, the sClosestDisturbance IS the climb point desired. So the - // check here should be "Am I aready there?" If so, THEN possibly climb. This previous check - // would have a soldier climbing any building, even if it was not the desired building. So - // WRONG WRONG WRONG - //if ( CanClimbFromHere ( pSoldier, fUp ) ) - if (pSoldier->sGridNo == sClosestDisturbance) + if ( fClimb && gGameExternalOptions.fZombieCanClimb )//&& pSoldier->aiData.usActionData == sClosestDisturbance) { - if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( APBPConstants[AP_CLIMBROOF] + MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST))) + // need to climb AND have enough APs to get there this turn + BOOLEAN fUp = TRUE; + if (pSoldier->pathing.bLevel > 0 ) + fUp = FALSE; + + if (!fUp) + DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) ); + + // As mentioned in the next part, the sClosestDisturbance IS the climb point desired. So the + // check here should be "Am I aready there?" If so, THEN possibly climb. This previous check + // would have a soldier climbing any building, even if it was not the desired building. So + // WRONG WRONG WRONG + //if ( CanClimbFromHere ( pSoldier, fUp ) ) + if (pSoldier->sGridNo == sClosestDisturbance) { - return( AI_ACTION_CLIMB_ROOF ); + if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( APBPConstants[AP_CLIMBROOF] + MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST))) + { + return( AI_ACTION_CLIMB_ROOF ); + } + } + else + { + // Do not overwrite the usActionData here. If there's no nearby climb point, the action data + // would become NOWHERE, and then the SEEK_ENEMY fallback would also fail. + // In fact, sClosestDisturbance has ALREADY calculated the closest climb point when climbing is + // necessary. The returned grid # in sClosestDisturbance is that climb point. So if climb is + // set, then use sClosestDisturbance as is. + //INT16 usClimbPoint = FindClosestClimbPoint(pSoldier, pSoldier->sGridNo , sClosestDisturbance , fUp ); + INT32 usClimbPoint = sClosestDisturbance; + if (!TileIsOutOfBounds(usClimbPoint)) + { + pSoldier->aiData.usActionData = usClimbPoint; + return( AI_ACTION_MOVE_TO_CLIMB ); + } + } + } + //if ( fClimb && pSoldier->aiData.usActionData == sClosestDisturbance) + //{ + // return( AI_ACTION_CLIMB_ROOF ); + //} + + + if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO || pSoldier->aiData.bAttitude == BRAVESOLO ) && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) + { + INT8 action = AI_ACTION_SEEK_OPPONENT; + INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sClosestDisturbance ); + if ( dist > MIN_FLANK_DIST_RED && dist < MAX_FLANK_DIST_RED ) + { + INT16 rdm = Random(6); + + switch (rdm) + { + case 1: + case 2: + case 3: + if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT ) + action = AI_ACTION_FLANK_LEFT ; + break; + default: + if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT ) + action = AI_ACTION_FLANK_RIGHT ; + break; + } + + if (action == AI_ACTION_SEEK_OPPONENT) { + return action; + } + } + else + return AI_ACTION_SEEK_OPPONENT ; + + pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sClosestDisturbance, action ); + + if (TileIsOutOfBounds(pSoldier->aiData.usActionData) || pSoldier->numFlanks >= MAX_FLANKS_RED ) + { + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,GetAPsCrouch( pSoldier, TRUE), AI_ACTION_SEEK_OPPONENT,0); + //pSoldier->numFlanks = 0; + if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) + { + // reserve APs for a possible crouch plus a shot + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + pSoldier->aiData.fAIFlags |= AI_CAUTIOUS; + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + return(AI_ACTION_SEEK_OPPONENT); + } + } + + else + { + return(AI_ACTION_SEEK_OPPONENT); + } + } + else + { + if ( action == AI_ACTION_FLANK_LEFT ) + pSoldier->flags.lastFlankLeft = TRUE; + else + pSoldier->flags.lastFlankLeft = FALSE; + + if ( pSoldier->lastFlankSpot != sClosestDisturbance) + pSoldier->numFlanks=0; + + + pSoldier->origDir = GetDirectionFromGridNo ( sClosestDisturbance, pSoldier); + pSoldier->lastFlankSpot = sClosestDisturbance; + pSoldier->numFlanks++; + return(action); } } else { - // Do not overwrite the usActionData here. If there's no nearby climb point, the action data - // would become NOWHERE, and then the SEEK_ENEMY fallback would also fail. - // In fact, sClosestDisturbance has ALREADY calculated the closest climb point when climbing is - // necessary. The returned grid # in sClosestDisturbance is that climb point. So if climb is - // set, then use sClosestDisturbance as is. - //INT16 usClimbPoint = FindClosestClimbPoint(pSoldier, pSoldier->sGridNo , sClosestDisturbance , fUp ); - INT32 usClimbPoint = sClosestDisturbance; - if (!TileIsOutOfBounds(usClimbPoint)) - { - pSoldier->aiData.usActionData = usClimbPoint; - return( AI_ACTION_MOVE_TO_CLIMB ); - } - } - } - //if ( fClimb && pSoldier->aiData.usActionData == sClosestDisturbance) - //{ - // return( AI_ACTION_CLIMB_ROOF ); - //} - - - if ( ( pSoldier->aiData.bAttitude == CUNNINGAID || pSoldier->aiData.bAttitude == CUNNINGSOLO || pSoldier->aiData.bAttitude == BRAVESOLO ) && gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) - { - INT8 action = AI_ACTION_SEEK_OPPONENT; - INT16 dist = PythSpacesAway ( pSoldier->sGridNo, sClosestDisturbance ); - if ( dist > MIN_FLANK_DIST_RED && dist < MAX_FLANK_DIST_RED ) - { - INT16 rdm = Random(6); - - switch (rdm) - { - case 1: - case 2: - case 3: - if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT ) - action = AI_ACTION_FLANK_LEFT ; - break; - default: - if ( pSoldier->aiData.bLastAction != AI_ACTION_FLANK_LEFT && pSoldier->aiData.bLastAction != AI_ACTION_FLANK_RIGHT ) - action = AI_ACTION_FLANK_RIGHT ; - break; - } - - if (action == AI_ACTION_SEEK_OPPONENT) { - return action; - } - } - else - return AI_ACTION_SEEK_OPPONENT ; - - pSoldier->aiData.usActionData = FindFlankingSpot (pSoldier, sClosestDisturbance, action ); - - if (TileIsOutOfBounds(pSoldier->aiData.usActionData) || pSoldier->numFlanks >= MAX_FLANKS_RED ) - { - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestDisturbance,GetAPsCrouch( pSoldier, TRUE), AI_ACTION_SEEK_OPPONENT,0); - //pSoldier->numFlanks = 0; + // let's be a bit cautious about going right up to a location without enough APs to shoot if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) { // reserve APs for a possible crouch plus a shot @@ -6900,1189 +6950,1152 @@ INT8 ZombieDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) return(AI_ACTION_SEEK_OPPONENT); } } - else { return(AI_ACTION_SEEK_OPPONENT); } + break; } - else - { - if ( action == AI_ACTION_FLANK_LEFT ) - pSoldier->flags.lastFlankLeft = TRUE; - else - pSoldier->flags.lastFlankLeft = FALSE; + } + } - if ( pSoldier->lastFlankSpot != sClosestDisturbance) - pSoldier->numFlanks=0; + // mark SEEKING as impossible for next time through while loop + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"couldn't SEEK...",1000); + #endif + bSeekPts = -99; + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't seek"); + } + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to watch"); + // if WATCHING is possible and at least as desirable as anything else + if ((bWatchPts > -90) && (bWatchPts >= bSeekPts) && (bWatchPts >= bHelpPts) ) + { + // take a look at our highest watch point... if it's still visible, turn to face it and then wait + bHighestWatchLoc = GetHighestVisibleWatchedLoc( pSoldier->ubID ); + //sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ); + if ( bHighestWatchLoc != -1 ) + { + // see if we need turn to face that location + ubOpponentDir = atan8( CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ),CenterY( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ) ); - pSoldier->origDir = GetDirectionFromGridNo ( sClosestDisturbance, pSoldier); - pSoldier->lastFlankSpot = sClosestDisturbance; - pSoldier->numFlanks++; - return(action); - } + // if soldier is not already facing in that direction, + // and the opponent is close enough that he could possibly be seen + if ( pSoldier->ubDirection != ubOpponentDir && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + { + // turn + pSoldier->aiData.usActionData = ubOpponentDir; + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + return(AI_ACTION_CHANGE_FACING); } else { - // let's be a bit cautious about going right up to a location without enough APs to shoot - if ( PythSpacesAway( pSoldier->aiData.usActionData, sClosestDisturbance ) < 5 || LocationToLocationLineOfSightTest( pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, sClosestDisturbance, pSoldier->pathing.bLevel, TRUE ) ) - { - // reserve APs for a possible crouch plus a shot - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier, sClosestDisturbance, (INT8) (MinAPsToAttack( pSoldier, sClosestDisturbance, ADDTURNCOST) + GetAPsCrouch( pSoldier, TRUE)), AI_ACTION_SEEK_OPPONENT, FLAG_CAUTIOUS ); - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { - pSoldier->aiData.fAIFlags |= AI_CAUTIOUS; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; - return(AI_ACTION_SEEK_OPPONENT); - } + // consider at least crouching + if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) + { + // end turn + return( AI_ACTION_END_TURN ); } - else - { - return(AI_ACTION_SEEK_OPPONENT); - } - break; } - } - } - // mark SEEKING as impossible for next time through while loop -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"couldn't SEEK...",1000); -#endif - bSeekPts = -99; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't seek"); - } - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to watch"); - // if WATCHING is possible and at least as desirable as anything else - if ((bWatchPts > -90) && (bWatchPts >= bSeekPts) && (bWatchPts >= bHelpPts) ) - { - // take a look at our highest watch point... if it's still visible, turn to face it and then wait - bHighestWatchLoc = GetHighestVisibleWatchedLoc( pSoldier->ubID ); - //sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ); - if ( bHighestWatchLoc != -1 ) - { - // see if we need turn to face that location - ubOpponentDir = atan8( CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ),CenterY( gsWatchedLoc[ pSoldier->ubID ][ bHighestWatchLoc ] ) ); - - // if soldier is not already facing in that direction, - // and the opponent is close enough that he could possibly be seen - if ( pSoldier->ubDirection != ubOpponentDir && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) - { - // turn - pSoldier->aiData.usActionData = ubOpponentDir; - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; - return(AI_ACTION_CHANGE_FACING); - } - else - { - // consider at least crouching - if ( gAnimControl[ pSoldier->usAnimState ].ubHeight != ANIM_PRONE ) - { - // end turn - return( AI_ACTION_END_TURN ); - } } + bWatchPts = -99; + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't watch"); + } - bWatchPts = -99; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't watch"); - } - - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to help"); - // if HELPING is possible and at least as desirable as seeking or hiding - if ((bHelpPts > -90) && (bHelpPts >= bSeekPts) && (bHelpPts >= bWatchPts )) - { -#ifdef AI_TIMING_TESTS - uiStartTime = GetJA2Clock(); -#endif - sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb ); -#ifdef AI_TIMING_TESTS - uiEndTime = GetJA2Clock(); - - guiRedHelpTimeTotal += (uiEndTime - uiStartTime); - guiRedHelpCounter++; -#endif - - if (!TileIsOutOfBounds(sClosestFriend)) + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: checking to help"); + // if HELPING is possible and at least as desirable as seeking or hiding + if ((bHelpPts > -90) && (bHelpPts >= bSeekPts) && (bHelpPts >= bWatchPts )) { - ////////////////////////////////////////////////////////////////////// - // GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED - ////////////////////////////////////////////////////////////////////// - pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestFriend,GetAPsCrouch( pSoldier, TRUE), AI_ACTION_SEEK_OPPONENT,0); + #ifdef AI_TIMING_TESTS + uiStartTime = GetJA2Clock(); + #endif + sClosestFriend = ClosestReachableFriendInTrouble(pSoldier, &fClimb ); + #ifdef AI_TIMING_TESTS + uiEndTime = GetJA2Clock(); + + guiRedHelpTimeTotal += (uiEndTime - uiStartTime); + guiRedHelpCounter++; + #endif - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + if (!TileIsOutOfBounds(sClosestFriend)) { -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d", - pSoldier->name,sClosestFriend,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - - if ( fClimb && gGameExternalOptions.fZombieCanClimb )//&& pSoldier->aiData.usActionData == sClosestFriend) + ////////////////////////////////////////////////////////////////////// + // GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED + ////////////////////////////////////////////////////////////////////// + pSoldier->aiData.usActionData = InternalGoAsFarAsPossibleTowards(pSoldier,sClosestFriend,GetAPsCrouch( pSoldier, TRUE), AI_ACTION_SEEK_OPPONENT,0); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { - // need to climb AND have enough APs to get there this turn - BOOLEAN fUp = TRUE; - if (pSoldier->pathing.bLevel > 0 ) - fUp = FALSE; + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - SEEKING FRIEND at %d, MOVING to %d", + pSoldier->name,sClosestFriend,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif - if (!fUp) - DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) ); - - // 0verhaul: Yet another chance to climb the wrong building and otherwise waste CPU power. - // We already know the climb point we want, which may not be here even if climbing is possible. - //if ( CanClimbFromHere ( pSoldier, fUp ) ) - if (pSoldier->sGridNo == sClosestFriend) + if ( fClimb && gGameExternalOptions.fZombieCanClimb )//&& pSoldier->aiData.usActionData == sClosestFriend) { - if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( APBPConstants[AP_CLIMBROOF] + MinAPsToAttack( pSoldier, sClosestFriend, ADDTURNCOST))) + // need to climb AND have enough APs to get there this turn + BOOLEAN fUp = TRUE; + if (pSoldier->pathing.bLevel > 0 ) + fUp = FALSE; + + if (!fUp) + DebugMsg ( TOPIC_JA2AI , DBG_LEVEL_3 , String("Soldier %d is climbing down",pSoldier->ubID) ); + + // 0verhaul: Yet another chance to climb the wrong building and otherwise waste CPU power. + // We already know the climb point we want, which may not be here even if climbing is possible. + //if ( CanClimbFromHere ( pSoldier, fUp ) ) + if (pSoldier->sGridNo == sClosestFriend) { - return( AI_ACTION_CLIMB_ROOF ); + if (IsActionAffordable(pSoldier) && pSoldier->bActionPoints >= ( APBPConstants[AP_CLIMBROOF] + MinAPsToAttack( pSoldier, sClosestFriend, ADDTURNCOST))) + { + return( AI_ACTION_CLIMB_ROOF ); + } } - } - else - { - pSoldier->aiData.usActionData = sClosestFriend; - //INT32 sClimbPoint = FindClosestClimbPoint(pSoldier, pSoldier->sGridNo , sClosestFriend , fUp ); - //if (!TileIsOutOfBounds(sClimbPoint)) + else { - //pSoldier->aiData.usActionData = sClimbPoint; - return( AI_ACTION_MOVE_TO_CLIMB ); + pSoldier->aiData.usActionData = sClosestFriend; + //INT32 sClimbPoint = FindClosestClimbPoint(pSoldier, pSoldier->sGridNo , sClosestFriend , fUp ); + //if (!TileIsOutOfBounds(sClimbPoint)) + { + //pSoldier->aiData.usActionData = sClimbPoint; + return( AI_ACTION_MOVE_TO_CLIMB ); + } } } + //if (fClimb && pSoldier->aiData.usActionData == sClosestFriend) + //{ + // return( AI_ACTION_CLIMB_ROOF ); + //} + return(AI_ACTION_SEEK_FRIEND); } - //if (fClimb && pSoldier->aiData.usActionData == sClosestFriend) - //{ - // return( AI_ACTION_CLIMB_ROOF ); - //} - return(AI_ACTION_SEEK_FRIEND); } + + // mark SEEKING as impossible for next time through while loop + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"couldn't HELP...",1000); + #endif + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't help"); + bHelpPts = -99; + } + } + } + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: nothing to do!"); + //////////////////////////////////////////////////////////////////////////// + // NOTHING USEFUL POSSIBLE! IF NPC IS CURRENTLY UNDER FIRE, TRY TO RUN AWAY + //////////////////////////////////////////////////////////////////////////// + } + + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: look around towards opponent"); + //////////////////////////////////////////////////////////////////////////// + // LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN + //////////////////////////////////////////////////////////////////////////// + + if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) + { + // determine the location of the known closest opponent + // (don't care if he's conscious, don't care if he's reachable at all) + sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); + + if (!TileIsOutOfBounds(sClosestOpponent)) + { + // determine direction from this soldier to the closest opponent + ubOpponentDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent)); + + // if soldier is not already facing in that direction, + // and the opponent is close enough that he could possibly be seen + // note, have to change this to use the level returned from ClosestKnownOpponent + sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0, CALC_FROM_ALL_DIRS ); + + if ((pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible)) + { + // set base chance according to orders + if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD)) + iChance = 50; + else // all other orders + iChance = 25; + + if (pSoldier->aiData.bAttitude == DEFENSIVE) + iChance += 25; + + if ( TANK( pSoldier ) ) + { + iChance += 50; } - // mark SEEKING as impossible for next time through while loop -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"couldn't HELP...",1000); -#endif + if ((INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + { + pSoldier->aiData.usActionData = ubOpponentDir; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: couldn't help"); - bHelpPts = -99; - } + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + return(AI_ACTION_CHANGE_FACING); + } + } } } - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: nothing to do!"); - //////////////////////////////////////////////////////////////////////////// - // NOTHING USEFUL POSSIBLE! IF NPC IS CURRENTLY UNDER FIRE, TRY TO RUN AWAY - //////////////////////////////////////////////////////////////////////////// - } - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: look around towards opponent"); - //////////////////////////////////////////////////////////////////////////// - // LOOK AROUND TOWARD CLOSEST KNOWN OPPONENT, IF KNOWN - //////////////////////////////////////////////////////////////////////////// - - if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) - { - // determine the location of the known closest opponent - // (don't care if he's conscious, don't care if he's reachable at all) - sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL); - - if (!TileIsOutOfBounds(sClosestOpponent)) + if ( TANK( pSoldier ) ) { - // determine direction from this soldier to the closest opponent - ubOpponentDir = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent)); - - // if soldier is not already facing in that direction, - // and the opponent is close enough that he could possibly be seen - // note, have to change this to use the level returned from ClosestKnownOpponent - sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0, CALC_FROM_ALL_DIRS ); - - if ((pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible)) + // try turning in a random direction as we still can't see anyone. + if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) { - // set base chance according to orders - if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD)) - iChance = 50; - else // all other orders - iChance = 25; - - if (pSoldier->aiData.bAttitude == DEFENSIVE) - iChance += 25; - - if ( TANK( pSoldier ) ) + sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL ); + + if (!TileIsOutOfBounds(sClosestDisturbance)) { - iChance += 50; + ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) ); + if ( pSoldier->ubDirection == ubOpponentDir ) + { + ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS ); + } + } + else + { + ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS ); } - if ((INT16)PreRandom(100) < iChance && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + if ( (pSoldier->ubDirection != ubOpponentDir) ) { - pSoldier->aiData.usActionData = ubOpponentDir; + if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints || (INT16)PreRandom(100) < 60) && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + { + pSoldier->aiData.usActionData = ubOpponentDir; -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - return(AI_ACTION_CHANGE_FACING); + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + + // limit turning a bit... if the last thing we did was also a turn, add a 60% chance of this being our last turn + if ( pSoldier->aiData.bLastAction == AI_ACTION_CHANGE_FACING && PreRandom( 100 ) < 60 ) + { + if ( gfTurnBasedAI ) + { + pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; + } + else + { + pSoldier->aiData.bNextAction = AI_ACTION_WAIT; + pSoldier->aiData.usNextActionData = (UINT16) REALTIME_AI_DELAY; + } + } + + return(AI_ACTION_CHANGE_FACING); + } } - } + } + + // that's it for tanks + return( AI_ACTION_NONE ); } - } - if ( TANK( pSoldier ) ) - { - // try turning in a random direction as we still can't see anyone. - if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) + + //////////////////////////////////////////////////////////////////////////// + // SWITCH TO GREEN: soldier does ordinary regular patrol, seeks friends + //////////////////////////////////////////////////////////////////////////// + + // if not in combat or under fire, and we COULD have moved, just chose not to + if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, TRUE, &fClimb))) ) + { + // addition: if soldier is bleeding then reduce bleeding and do nothing + /*if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD ) + { + // no! zombies don't bandage themselves! + // reduce bleeding by 1 point per AP (in RT, APs will get recalculated so it's okay) + // pSoldier->bBleeding = __max( 0, pSoldier->bBleeding - (pSoldier->bActionPoints/2) ); + return( AI_ACTION_NONE ); // will end-turn/wait depending on whether we're in TB or realtime + }*/ + #ifdef RECORDNET + fprintf(NetDebugFile,"\tZombieDecideActionRed: guynum %d switching to GREEN AI...\n",pSoldier->ubID); + #endif + + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"- chose to SKIP all RED actions, BYPASSES to GREEN!",1000); + #endif + + // Skip RED until new situation/next turn, 30% extra chance to do GREEN actions + pSoldier->aiData.bBypassToGreen = 30; + return(ZombieDecideActionGreen(pSoldier)); + } + + + //////////////////////////////////////////////////////////////////////////// + // IF UNDER FIRE, FACE THE MOST IMPORTANT NOISE WE KNOW + //////////////////////////////////////////////////////////////////////////// + + if ( pSoldier->aiData.bUnderFire && pSoldier->bActionPoints >= (pSoldier->bInitialActionPoints - GetAPsToLook( pSoldier ) ) ) { sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL ); if (!TileIsOutOfBounds(sClosestDisturbance)) { ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) ); - if ( pSoldier->ubDirection == ubOpponentDir ) + if ( pSoldier->ubDirection != ubOpponentDir ) { - ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS ); - } - } - else - { - ubOpponentDir = (UINT8) PreRandom( NUM_WORLD_DIRECTIONS ); - } - - if ( (pSoldier->ubDirection != ubOpponentDir) ) - { - if ( (pSoldier->bActionPoints == pSoldier->bInitialActionPoints || (INT16)PreRandom(100) < 60) && pSoldier->InternalIsValidStance( ubOpponentDir, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) - { - pSoldier->aiData.usActionData = ubOpponentDir; - -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - TURNS TOWARDS CLOSEST ENEMY to face direction %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - - // limit turning a bit... if the last thing we did was also a turn, add a 60% chance of this being our last turn - if ( pSoldier->aiData.bLastAction == AI_ACTION_CHANGE_FACING && PreRandom( 100 ) < 60 ) + if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints ) { - if ( gfTurnBasedAI ) - { - pSoldier->aiData.bNextAction = AI_ACTION_END_TURN; - } - else - { - pSoldier->aiData.bNextAction = AI_ACTION_WAIT; - pSoldier->aiData.usNextActionData = (UINT16) REALTIME_AI_DELAY; - } + pSoldier->aiData.usActionData = ubOpponentDir; + return( AI_ACTION_CHANGE_FACING ); } - - return(AI_ACTION_CHANGE_FACING); } } } + + //////////////////////////////////////////////////////////////////////////// + // DO NOTHING: Not enough points left to move, so save them for next turn + //////////////////////////////////////////////////////////////////////////// + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: do nothing at all...")); + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"- DOES NOTHING (RED)",1000); + #endif - // that's it for tanks - return( AI_ACTION_NONE ); - } - - - //////////////////////////////////////////////////////////////////////////// - // SWITCH TO GREEN: soldier does ordinary regular patrol, seeks friends - //////////////////////////////////////////////////////////////////////////// - - // if not in combat or under fire, and we COULD have moved, just chose not to - if ( (pSoldier->aiData.bAlertStatus != STATUS_BLACK) && !pSoldier->aiData.bUnderFire && ubCanMove && (!gfTurnBasedAI || pSoldier->bActionPoints >= pSoldier->bInitialActionPoints) && ( TileIsOutOfBounds(ClosestReachableDisturbance(pSoldier, TRUE, &fClimb))) ) - { - // addition: if soldier is bleeding then reduce bleeding and do nothing - /*if ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD ) - { - // no! zombies don't bandage themselves! - // reduce bleeding by 1 point per AP (in RT, APs will get recalculated so it's okay) - // pSoldier->bBleeding = __max( 0, pSoldier->bBleeding - (pSoldier->bActionPoints/2) ); - return( AI_ACTION_NONE ); // will end-turn/wait depending on whether we're in TB or realtime - }*/ -#ifdef RECORDNET - fprintf(NetDebugFile,"\tZombieDecideActionRed: guynum %d switching to GREEN AI...\n",pSoldier->ubID); -#endif - -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"- chose to SKIP all RED actions, BYPASSES to GREEN!",1000); -#endif - - // Skip RED until new situation/next turn, 30% extra chance to do GREEN actions - pSoldier->aiData.bBypassToGreen = 30; - return(ZombieDecideActionGreen(pSoldier)); - } - - - //////////////////////////////////////////////////////////////////////////// - // IF UNDER FIRE, FACE THE MOST IMPORTANT NOISE WE KNOW - //////////////////////////////////////////////////////////////////////////// - - if ( pSoldier->aiData.bUnderFire && pSoldier->bActionPoints >= (pSoldier->bInitialActionPoints - GetAPsToLook( pSoldier ) ) ) - { - sClosestDisturbance = MostImportantNoiseHeard( pSoldier, NULL, NULL, NULL ); - - if (!TileIsOutOfBounds(sClosestDisturbance)) - { - ubOpponentDir = atan8( CenterX( pSoldier->sGridNo ), CenterY( pSoldier->sGridNo ), CenterX( sClosestDisturbance ), CenterY( sClosestDisturbance ) ); - if ( pSoldier->ubDirection != ubOpponentDir ) - { - if ( !gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints ) - { - pSoldier->aiData.usActionData = ubOpponentDir; - return( AI_ACTION_CHANGE_FACING ); - } - } - } - } - - //////////////////////////////////////////////////////////////////////////// - // DO NOTHING: Not enough points left to move, so save them for next turn - //////////////////////////////////////////////////////////////////////////// - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: do nothing at all...")); -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"- DOES NOTHING (RED)",1000); -#endif - - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); -} - -INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier) -{ - INT32 iCoverPercentBetter = 0; - INT32 iOffense, iDefense, iChance; - INT32 sClosestOpponent,sBestCover = NOWHERE; - INT16 ubMinAPCost; - UINT8 ubCanMove; - INT8 bInWater,bInDeepWater; - INT8 bDirection; - UINT8 ubBestAttackAction = AI_ACTION_NONE; - INT8 bCanAttack; - INT8 bWeaponIn; - BOOLEAN fTryPunching = FALSE; -#ifdef DEBUGDECISIONS - STR16 tempstr; -#endif - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); - - ATTACKTYPE BestStab = {},BestAttack = {}; - UINT8 ubBestStance = 1; - BOOLEAN fChangeStanceFirst; // before firing - INT16 ubBurstAPs; - - BOOLEAN fAllowCoverCheck = FALSE; - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack"); - - // if we have absolutely no action points, we can't do a thing under BLACK! - if (!pSoldier->bActionPoints) - { pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); } - // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) - ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); - - // determine if we happen to be in water (in which case we're in BIG trouble!) - bInWater = Water( pSoldier->sGridNo ); - bInDeepWater = WaterTooDeepForAttacks( pSoldier->sGridNo ); - - // calculate our morale - pSoldier->aiData.bAIMorale = CalcMorale(pSoldier); - - //////////////////////////////////////////////////////////////////////////// - // STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND - //////////////////////////////////////////////////////////////////////////// - - // if soldier in water/gas has enough APs left to move at least 1 square - if ( bInDeepWater && ubCanMove) + INT8 ZombieDecideActionBlack(SOLDIERTYPE *pSoldier) { - pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); - - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif + INT32 iCoverPercentBetter = 0; + INT32 iOffense, iDefense, iChance; + INT32 sClosestOpponent,sBestCover = NOWHERE; + INT16 ubMinAPCost; + UINT8 ubCanMove; + INT8 bInWater,bInDeepWater; + INT8 bDirection; + UINT8 ubBestAttackAction = AI_ACTION_NONE; + INT8 bCanAttack; + INT8 bWeaponIn; + BOOLEAN fTryPunching = FALSE; + #ifdef DEBUGDECISIONS + STR16 tempstr; + #endif + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: soldier = %d, orders = %d, attitude = %d",pSoldier->ubID,pSoldier->aiData.bOrders,pSoldier->aiData.bAttitude)); - return(AI_ACTION_LEAVE_WATER_GAS); + ATTACKTYPE BestStab = {},BestAttack = {}; + UINT8 ubBestStance = 1; + BOOLEAN fChangeStanceFirst; // before firing + INT16 ubBurstAPs; + + BOOLEAN fAllowCoverCheck = FALSE; + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack"); + + // if we have absolutely no action points, we can't do a thing under BLACK! + if (!pSoldier->bActionPoints) + { + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); } - // couldn't find ANY land within 25 tiles(!), this should never happen... - - // look for best place to RUN AWAY to (farthest from the closest threat) - pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier); + // can this guy move to any of the neighbouring squares ? (sets TRUE/FALSE) + ubCanMove = (pSoldier->bActionPoints >= MinPtsToMove(pSoldier)); - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + // determine if we happen to be in water (in which case we're in BIG trouble!) + bInWater = Water( pSoldier->sGridNo ); + bInDeepWater = WaterTooDeepForAttacks( pSoldier->sGridNo ); + + // calculate our morale + pSoldier->aiData.bAIMorale = CalcMorale(pSoldier); + + //////////////////////////////////////////////////////////////////////////// + // STUCK IN WATER OR GAS, NO COVER, GO TO NEAREST SPOT OF UNGASSED LAND + //////////////////////////////////////////////////////////////////////////// + + // if soldier in water/gas has enough APs left to move at least 1 square + if ( bInDeepWater && ubCanMove) { -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - NO LAND NEAR, RUNNING AWAY to grid %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif - - return(AI_ACTION_RUN_AWAY); - } - - if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE ) - pSoldier->aiData.bAIMorale = MORALE_FEARLESS; // go nuts... - } - - - //////////////////////////////////////////////////////////////////////////// - // SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY - //////////////////////////////////////////////////////////////////////////// - - - // NPCs in water/tear gas without masks are not permitted to shoot/stab/throw - if ((pSoldier->bActionPoints < 2) || bInDeepWater || pSoldier->aiData.bRTPCombat == RTP_COMBAT_REFRAIN) - { - bCanAttack = FALSE; - } - else if (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) - { - bCanAttack = TRUE; - fTryPunching = TRUE; - } - else - { - do - { - - bCanAttack = CanNPCAttack(pSoldier); - if (bCanAttack != TRUE) + pSoldier->aiData.usActionData = FindNearestUngassedLand(pSoldier); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) { - if (bCanAttack == NOSHOOT_NOAMMO && ubCanMove && !pSoldier->aiData.bNeutral) - { - //CHRISL: We need to know which weapon has no ammo in case the soldier is holding a weapoin in SECONDHANDPOS - if(pSoldier->inv[SECONDHANDPOS].exists() == true ) - { - // the current weapon appears is useless right now! - pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE; + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - SEEKING NEAREST UNGASSED LAND at grid %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif - // drop item - return AI_ACTION_DROP_ITEM; - } - else - { - return( pSoldier->aiData.bAction ); - } - } - else - { - bCanAttack = FALSE; - } + return(AI_ACTION_LEAVE_WATER_GAS); } - } while( bCanAttack != TRUE && bCanAttack != FALSE ); -#ifdef RETREAT_TESTING - bCanAttack = FALSE; -#endif + // couldn't find ANY land within 25 tiles(!), this should never happen... - if (!bCanAttack) + // look for best place to RUN AWAY to (farthest from the closest threat) + pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier); + + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - NO LAND NEAR, RUNNING AWAY to grid %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + + return(AI_ACTION_RUN_AWAY); + } + + if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE ) + pSoldier->aiData.bAIMorale = MORALE_FEARLESS; // go nuts... + } + + + //////////////////////////////////////////////////////////////////////////// + // SOLDIER CAN ATTACK IF NOT IN WATER/GAS AND NOT DOING SOMETHING TOO FUNKY + //////////////////////////////////////////////////////////////////////////// + + + // NPCs in water/tear gas without masks are not permitted to shoot/stab/throw + if ((pSoldier->bActionPoints < 2) || bInDeepWater || pSoldier->aiData.bRTPCombat == RTP_COMBAT_REFRAIN) + { + bCanAttack = FALSE; + } + else if (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) { bCanAttack = TRUE; fTryPunching = TRUE; } - } - - BestStab.ubPossible = FALSE; // by default, assume Stabbing isn't possible - - BestAttack.ubChanceToReallyHit = 0; - - // if we are able attack - if (bCanAttack) - { - pSoldier->bAimShotLocation = AIM_SHOT_RANDOM; - - ////////////////////////////////////////////////////////////////////////// - // GO STAB AN OPPONENT WITH A KNIFE - ////////////////////////////////////////////////////////////////////////// - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"GO STAB AN OPPONENT WITH A KNIFE"); - // if soldier has a knife in his hand - bWeaponIn = FindAIUsableObjClass( pSoldier, (IC_BLADE | IC_THROWING_KNIFE) ); - - ///////////////////////////////////////////////////////////////////////////////////////////////////// - // SANDRO - even if we don't have any blade, calculate how much damage we could do unarmed - if ( !TANK( pSoldier) ) - { - bWeaponIn = FindAIUsableObjClass( pSoldier, IC_PUNCH ); - if (bWeaponIn == NO_SLOT) // if no punch-type weapon found, just calculate it with empty hands - { - bWeaponIn = FindEmptySlotWithin( pSoldier, HANDPOS, NUM_INV_SLOTS ); - } - if (bWeaponIn != NO_SLOT) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"try to punch"); - BestStab.bWeaponIn = bWeaponIn; - // if it's in his holster, swap it into his hand temporarily - if (bWeaponIn != HANDPOS) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: about to rearrange pocket before punch check"); - RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); - } - - // get the minimum cost to attack with punch - ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST); - // if we can afford the minimum AP cost to punch - if (pSoldier->bActionPoints >= ubMinAPCost) - { - // then look around for a worthy target (which sets BestStab.ubPossible) - CalcBestStab(pSoldier,&BestStab, FALSE); - - if (BestStab.ubPossible) - { - // if we have not enough APs to deal at least two or three punches, - // reduce the attack value as one punch ain't much - if( gGameOptions.fNewTraitSystem ) - { - // if we are not specialized, reduce the attack attractiveness generaly - if ( !HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) ) - { - // if too far and not having APs for at least 3 hits no way to attack - if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + (2 * (ApsToPunch( pSoldier )))) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1) ) - { - BestStab.ubPossible = 0; - BestStab.iAttackValue = 0; - } - } - else - { - if (PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1) - { - BestStab.iAttackValue = (BestStab.iAttackValue * 2); - } - // if too far and not having APs for at least 2 hits - else if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + ApsToPunch( pSoldier )) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1)) - { - BestStab.iAttackValue /= 3; - } - } - } - else - { - if ( !HAS_SKILL_TRAIT( pSoldier, MARTIALARTS_OT ) && !HAS_SKILL_TRAIT( pSoldier, HANDTOHAND_OT ) ) - { - // if we are not specialized, reduce the attack attractiveness generaly - BestStab.iAttackValue /= 4; - // if too far and not having APs for at least 3 hits - if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + (2 * (ApsToPunch( pSoldier )))) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget <= 1)) ) - { - BestStab.ubPossible = 0; - BestStab.iAttackValue = 0; - } - } - else - { - BestStab.iAttackValue = ((BestStab.iAttackValue * 3)/2); - - if (PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1) - { - BestStab.iAttackValue = ((BestStab.iAttackValue * 3)/2); - } - // if too far and not having APs for at least 2 hits - else if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + ApsToPunch( pSoldier )) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget <= 1)) ) - { - BestStab.iAttackValue /= 3; - } - } - } - // now we KNOW FOR SURE that we will do something (stab, at least) - NPCDoesAct(pSoldier); - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to punch"); - } - } - // if it was in his holster, swap it back into his holster for now - if (bWeaponIn != HANDPOS) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to rearrange pocket after punch check"); - RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); - } - } - } - ///////////////////////////////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE - ////////////////////////////////////////////////////////////////////////// - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE"); - - BestAttack.iAttackValue = 0; - - if (BestStab.ubPossible && ((BestStab.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE))) - { - // zombies are always punching... - BestAttack.iAttackValue = BestStab.iAttackValue; - ubBestAttackAction = AI_ACTION_KNIFE_MOVE; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"best action = move to punch"); - //////////////////////////////////////////////////////////////////////////////////// - } - - if ( ( ubBestAttackAction == AI_ACTION_NONE ) && fTryPunching ) - { - // nothing (else) to attack with so let's try hand-to-hand - bWeaponIn = FindObj( pSoldier, NOTHING, HANDPOS, NUM_INV_SLOTS ); - - if (bWeaponIn != NO_SLOT) - { - BestStab.bWeaponIn = bWeaponIn; - // if it's in his holster, swap it into his hand temporarily - if (bWeaponIn != HANDPOS) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap knife into hand"); - RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); - } - - // get the minimum cost to attack by HTH - ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST); - - // if we can afford the minimum AP cost to use HTH combat - if (pSoldier->bActionPoints >= ubMinAPCost) - { - // then look around for a worthy target (which sets BestStab.ubPossible) - CalcBestStab(pSoldier,&BestStab, FALSE); - - if (BestStab.ubPossible && pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE) - { - // now we KNOW FOR SURE that we will do something (stab, at least) - NPCDoesAct(pSoldier); - ubBestAttackAction = AI_ACTION_KNIFE_MOVE; - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to move to stab"); - } - } - - // if it was in his holster, swap it back into his holster for now - if (bWeaponIn != HANDPOS) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: about to put knife away"); - RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); - } - } - } - - - // copy the information on the best action selected into BestAttack struct - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"copy the information on the best action selected into BestAttack struct"); - switch (ubBestAttackAction) - { - case AI_ACTION_KNIFE_MOVE: - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = stab with a knife"); - memcpy(&BestAttack,&BestStab,sizeof(BestAttack)); - break; - - default: - // set to empty - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = no good attack"); - memset( &BestAttack, 0, sizeof( BestAttack ) ); - break; - } - } - - // NB a desire of 4 or more is only achievable by brave/aggressive guys with high morale - UINT16 usRange = GetModifiedGunRange(pSoldier->inv[ BestAttack.bWeaponIn ].usItem); - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)"); - ////////////////////////////////////////////////////////////////////////// - // IF NECESSARY, DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER) - ////////////////////////////////////////////////////////////////////////// - - // if both are possible - if ((ubBestAttackAction != AI_ACTION_NONE) && ( !TileIsOutOfBounds(sBestCover))) - { - // gotta compare their merits and select the more desirable option - iOffense = BestAttack.ubChanceToReallyHit; - iDefense = iCoverPercentBetter; - - // based on how we feel about the situation, decide whether to attack first - switch (pSoldier->aiData.bAIMorale) - { - case MORALE_FEARLESS: - iOffense += iOffense / 2; // increase 50% - break; - - case MORALE_CONFIDENT: - iOffense += iOffense / 4; // increase 25% - break; - - case MORALE_NORMAL: - break; - - case MORALE_WORRIED: - iDefense += iDefense / 4; // increase 25% - break; - - case MORALE_HOPELESS: - iDefense += iDefense / 2; // increase 50% - break; - } - - - // smart guys more likely to try to stay alive, dolts more likely to shoot! - if (pSoldier->stats.bWisdom >= 50) //Madd: reduced the wisdom required to want to live... - iDefense += 10; - else if (pSoldier->stats.bWisdom < 30) - iDefense -= 10; - - // some orders are more offensive, others more defensive - if (pSoldier->aiData.bOrders == SEEKENEMY) - iOffense += 10; - else if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) || pSoldier->aiData.bOrders == SNIPER ) - iDefense += 10; - - switch (pSoldier->aiData.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 ATTACKSLAYONLY:iOffense += 30; break; - } - -#ifdef DEBUGDECISIONS - STR tempstr=""; - sprintf( tempstr, "%s - CHOICE: iOffense = %d, iDefense = %d\n", - pSoldier->name,iOffense,iDefense); - DebugAI( tempstr ); -#endif - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: if his defensive instincts win out, forget all about the attack"); - // if his defensive instincts win out, forget all about the attack - if (iDefense > iOffense) - ubBestAttackAction = AI_ACTION_NONE; - } - - - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: is attack still desirable? ubBestAttackAction = %d",ubBestAttackAction)); - - // if attack is still desirable (meaning it's also preferred to taking cover) - if (ubBestAttackAction != AI_ACTION_NONE) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: attack is still desirable (meaning it's also preferred to taking cover)"); - // if we wanted to be REALLY mean, we could look at chance to hit and decide whether - // to shoot at the head... - - fChangeStanceFirst = FALSE; - - // default settings - //POSSIBLE STRUCTURE CHANGE PROBLEM, NOT CURRENTLY CHANGED. GOTTHARD 7/14/08 - pSoldier->aiData.bAimTime = BestAttack.ubAimTime; - pSoldier->bDoBurst = 0; - - // HEADROCK HAM 3.6: bAimTime represents how MANY aiming levels are used, not how much APs they cost necessarily. - INT16 sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); - - if (ubBestAttackAction == AI_ACTION_KNIFE_MOVE && gGameOptions.fNewTraitSystem) - { - pSoldier->aiData.bAimTime = 0; - - if (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_PUNCH) - { - if (HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT)) - { - if( PreRandom( (gGameExternalOptions.fEnhancedCloseCombatSystem ? 2 : 0) + 2 * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT) ) > 2 ) - { - pSoldier->aiData.bAimTime = (gGameExternalOptions.fEnhancedCloseCombatSystem ? gSkillTraitValues.ubModifierForAPsAddedOnAimedPunches : 6); - } - } - } - else - { - if (HAS_SKILL_TRAIT( pSoldier, MELEE_NT)) - { - if( PreRandom( gGameExternalOptions.fEnhancedCloseCombatSystem ? 3 : 1 ) > 0 ) - { - pSoldier->aiData.bAimTime = (gGameExternalOptions.fEnhancedCloseCombatSystem ? gSkillTraitValues.ubModifierForAPsAddedOnAimedBladedAttackes : 6); - } - } - } - } - - ////////////////////////////////////////////////////////////////////////// - // OTHERWISE, JUST GO AHEAD & ATTACK! - ////////////////////////////////////////////////////////////////////////// - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"OTHERWISE, JUST GO AHEAD & ATTACK!"); - - // swap weapon to hand if necessary - if (BestAttack.bWeaponIn != HANDPOS) - { - DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap weapon into hand"); - RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER); - } - - if (fChangeStanceFirst) - { // currently only for guns... - pSoldier->aiData.bNextAction = AI_ACTION_FIRE_GUN; - pSoldier->aiData.usNextActionData = BestAttack.sTarget; - pSoldier->aiData.bNextTargetLevel = BestAttack.bTargetLevel; - pSoldier->aiData.usActionData = ubBestStance; - return( AI_ACTION_CHANGE_STANCE ); - } else { - pSoldier->aiData.usActionData = BestAttack.sTarget; - pSoldier->bTargetLevel = BestAttack.bTargetLevel; - -#ifdef DEBUGDECISIONS - STR tempstr=""; - sprintf( 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,pSoldier->name , - BestAttack.sTarget,BestAttack.ubAimTime ); - DebugAI( tempstr); -#endif - - //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->aiData.bRTPCombat,ubBestAttackAction )); - //should be a bug - DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier ),pSoldier->aiData.bRTPCombat,ubBestAttackAction )); - if (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_LAUNCHER && IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier )) && - (pSoldier->bTeam != gbPlayerNum || pSoldier->aiData.bRTPCombat == RTP_COMBAT_AGGRESSIVE) ) + do { - DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing burst calc"); - ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier ); - - if ( (pSoldier->bActionPoints - BestAttack.ubAPCost) >= ubBurstAPs ) + bCanAttack = CanNPCAttack(pSoldier); + if (bCanAttack != TRUE) { - // Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4 - if ( TANK( pSoldier ) ) + if (bCanAttack == NOSHOOT_NOAMMO && ubCanMove && !pSoldier->aiData.bNeutral) { - iChance = 100; - } - else - { - iChance = 50; - switch (pSoldier->aiData.bAttitude) + //CHRISL: We need to know which weapon has no ammo in case the soldier is holding a weapoin in SECONDHANDPOS + if(pSoldier->inv[SECONDHANDPOS].exists() == true ) { - case DEFENSIVE: iChance += -5; break; - case BRAVESOLO: iChance += 5; break; - case BRAVEAID: iChance += 5; break; - case CUNNINGSOLO: iChance += 0; break; - case CUNNINGAID: iChance += 0; break; - case AGGRESSIVE: iChance += 10; break; - case ATTACKSLAYONLY:iChance += 30; break; - } - // increase chance based on proximity and difficulty of enemy - DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to gl burst")); - iChance += ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) ); - } + // the current weapon appears is useless right now! + pSoldier->inv[HANDPOS].fFlags |= OBJECT_AI_UNUSABLE; - if ( (INT32) PreRandom( 100 ) < iChance) - { - DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing GL burst"); - BestAttack.ubAimTime = BURSTING; - BestAttack.ubAPCost = BestAttack.ubAPCost + CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[HANDPOS]), pSoldier ); - // check for spread burst possibilities - if (pSoldier->aiData.bAttitude != ATTACKSLAYONLY) - { - CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel ); - } - pSoldier->bDoBurst = 1; - - } - } - } - - - return(ubBestAttackAction); - } - - } - - // end of tank AI - if ( TANK( pSoldier ) ) - { - return( AI_ACTION_NONE ); - } - - // get the location of the closest reachable opponent - /* Flugente 22.02.2012 - A few clarifications: I changed ClosestSeenOpponent so that for zombies, this function also returns an opponent if he is on the - * roof of a building, we are not, but our GridNo belongs to that same building. - * If that is the case, it is clear that we have to get on that roof. However, we cannot do that in BlackState. If, by pure chance, we can still see our - * enemy, we cannot climb (there is no climbing option in BlackState sofar). - * So, I changed the code so that now we will climb the roof. - */ - INT32 targetGridNo = -1; - INT8 targetbLevel = 0; - sClosestOpponent = ClosestSeenOpponentforZombie(pSoldier, &targetGridNo, &targetbLevel); - if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE ) - { - if (!TileIsOutOfBounds(sClosestOpponent) && ! ( (targetbLevel != pSoldier->pathing.bLevel) && SameBuilding( pSoldier->sGridNo, targetGridNo ) ) ) - { - ////////////////////////////////////////////////////////////////////// - // GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT - ////////////////////////////////////////////////////////////////////// - - // try to move towards him - pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_GET_CLOSER); - - // if it's possible - if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) - { - if (targetbLevel != pSoldier->pathing.bLevel && gGameExternalOptions.fZombieCanClimb ) - return(AI_ACTION_MOVE_TO_CLIMB); - else - return(AI_ACTION_SEEK_OPPONENT); - } - } - // The situation mentioned above happens... - else if ( (targetbLevel != pSoldier->pathing.bLevel) && SameBuilding( pSoldier->sGridNo, targetGridNo ) ) - { - if ( gGameExternalOptions.fZombieCanClimb ) - { - if (!TileIsOutOfBounds(targetGridNo) ) - { - // need to climb AND have enough APs to get there this turn - BOOLEAN fUp = TRUE; - if (pSoldier->pathing.bLevel > 0 ) - fUp = FALSE; - - if ( pSoldier->bActionPoints > GetAPsToClimbRoof ( pSoldier, fUp ) ) - { - pSoldier->aiData.usActionData = targetGridNo;//FindClosestClimbPoint(pSoldier, fUp ); - - // Necessary test: can we climb up at this position? It might happen that our target is directly above us, then we'll have to move - INT8 newdirection; - if ( ( fUp && FindHeigherLevel( pSoldier, pSoldier->sGridNo, pSoldier->ubDirection, &newdirection ) ) || ( !fUp && FindLowerLevel( pSoldier, pSoldier->sGridNo, pSoldier->ubDirection, &newdirection ) ) ) - { - return( AI_ACTION_CLIMB_ROOF ); + // drop item + return AI_ACTION_DROP_ITEM; } else { - return(AI_ACTION_SEEK_OPPONENT); + return( pSoldier->aiData.bAction ); } } - } + else + { + bCanAttack = FALSE; + } + } + } while( bCanAttack != TRUE && bCanAttack != FALSE ); + + #ifdef RETREAT_TESTING + bCanAttack = FALSE; + #endif + + if (!bCanAttack) + { + bCanAttack = TRUE; + fTryPunching = TRUE; } } - } + + BestStab.ubPossible = FALSE; // by default, assume Stabbing isn't possible + + BestAttack.ubChanceToReallyHit = 0; - //////////////////////////////////////////////////////////////////////////// - // TURN TO FACE CLOSEST KNOWN OPPONENT (IF NOT FACING THERE ALREADY) - //////////////////////////////////////////////////////////////////////////// - - if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) - { - // hopeless guys shouldn't waste their time this way, UNLESS they CAN move - // but chose not to to get this far (which probably means they're cornered) - // ALSO, don't bother turning if we're already aiming a gun - if ( !gfHiddenInterrupt && ((pSoldier->aiData.bAIMorale > MORALE_HOPELESS) || ubCanMove) && !AimingGun(pSoldier)) + // if we are able attack + if (bCanAttack) { - // determine the location of the known closest opponent - // (don't care if he's conscious, don't care if he's reachable at all) + pSoldier->bAimShotLocation = AIM_SHOT_RANDOM; + + ////////////////////////////////////////////////////////////////////////// + // GO STAB AN OPPONENT WITH A KNIFE + ////////////////////////////////////////////////////////////////////////// + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"GO STAB AN OPPONENT WITH A KNIFE"); + // if soldier has a knife in his hand + bWeaponIn = FindAIUsableObjClass( pSoldier, (IC_BLADE | IC_THROWING_KNIFE) ); - sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL); - // if we have a closest reachable opponent - if (!TileIsOutOfBounds(sClosestOpponent)) + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // SANDRO - even if we don't have any blade, calculate how much damage we could do unarmed + if ( !TANK( pSoldier) ) { - bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent)); - - // if we're not facing towards him - if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + bWeaponIn = FindAIUsableObjClass( pSoldier, IC_PUNCH ); + if (bWeaponIn == NO_SLOT) // if no punch-type weapon found, just calculate it with empty hands { - pSoldier->aiData.usActionData = bDirection; + bWeaponIn = FindEmptySlotWithin( pSoldier, HANDPOS, NUM_INV_SLOTS ); + } + if (bWeaponIn != NO_SLOT) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"try to punch"); + BestStab.bWeaponIn = bWeaponIn; + // if it's in his holster, swap it into his hand temporarily + if (bWeaponIn != HANDPOS) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: about to rearrange pocket before punch check"); + RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); + } -#ifdef DEBUGDECISIONS - sprintf(tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->aiData.usActionData); - AIPopMessage(tempstr); -#endif + // get the minimum cost to attack with punch + ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST); + // if we can afford the minimum AP cost to punch + if (pSoldier->bActionPoints >= ubMinAPCost) + { + // then look around for a worthy target (which sets BestStab.ubPossible) + CalcBestStab(pSoldier,&BestStab, FALSE); - return(AI_ACTION_CHANGE_FACING); + if (BestStab.ubPossible) + { + // if we have not enough APs to deal at least two or three punches, + // reduce the attack value as one punch ain't much + if( gGameOptions.fNewTraitSystem ) + { + // if we are not specialized, reduce the attack attractiveness generaly + if ( !HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT ) ) + { + // if too far and not having APs for at least 3 hits no way to attack + if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + (2 * (ApsToPunch( pSoldier )))) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1) ) + { + BestStab.ubPossible = 0; + BestStab.iAttackValue = 0; + } + } + else + { + if (PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1) + { + BestStab.iAttackValue = (BestStab.iAttackValue * 2); + } + // if too far and not having APs for at least 2 hits + else if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + ApsToPunch( pSoldier )) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1)) + { + BestStab.iAttackValue /= 3; + } + } + } + else + { + if ( !HAS_SKILL_TRAIT( pSoldier, MARTIALARTS_OT ) && !HAS_SKILL_TRAIT( pSoldier, HANDTOHAND_OT ) ) + { + // if we are not specialized, reduce the attack attractiveness generaly + BestStab.iAttackValue /= 4; + // if too far and not having APs for at least 3 hits + if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + (2 * (ApsToPunch( pSoldier )))) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget <= 1)) ) + { + BestStab.ubPossible = 0; + BestStab.iAttackValue = 0; + } + } + else + { + BestStab.iAttackValue = ((BestStab.iAttackValue * 3)/2); + + if (PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget) <= 1) + { + BestStab.iAttackValue = ((BestStab.iAttackValue * 3)/2); + } + // if too far and not having APs for at least 2 hits + else if (((CalcTotalAPsToAttack( pSoldier,BestStab.sTarget,ADDTURNCOST, 0 ) + ApsToPunch( pSoldier )) > pSoldier->bActionPoints) && !(PythSpacesAway(pSoldier->sGridNo, BestStab.sTarget <= 1)) ) + { + BestStab.iAttackValue /= 3; + } + } + } + // now we KNOW FOR SURE that we will do something (stab, at least) + NPCDoesAct(pSoldier); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to punch"); + } + } + // if it was in his holster, swap it back into his holster for now + if (bWeaponIn != HANDPOS) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"about to rearrange pocket after punch check"); + RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); + } + } + } + ///////////////////////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////// + // CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE + ////////////////////////////////////////////////////////////////////////// + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CHOOSE THE BEST TYPE OF ATTACK OUT OF THOSE FOUND TO BE POSSIBLE"); + + BestAttack.iAttackValue = 0; + + if (BestStab.ubPossible && ((BestStab.iAttackValue > BestAttack.iAttackValue) || (ubBestAttackAction == AI_ACTION_NONE))) + { + // zombies are always punching... + BestAttack.iAttackValue = BestStab.iAttackValue; + ubBestAttackAction = AI_ACTION_KNIFE_MOVE; + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"best action = move to punch"); + //////////////////////////////////////////////////////////////////////////////////// + } + + if ( ( ubBestAttackAction == AI_ACTION_NONE ) && fTryPunching ) + { + // nothing (else) to attack with so let's try hand-to-hand + bWeaponIn = FindObj( pSoldier, NOTHING, HANDPOS, NUM_INV_SLOTS ); + + if (bWeaponIn != NO_SLOT) + { + BestStab.bWeaponIn = bWeaponIn; + // if it's in his holster, swap it into his hand temporarily + if (bWeaponIn != HANDPOS) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap knife into hand"); + RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); + } + + // get the minimum cost to attack by HTH + ubMinAPCost = MinAPsToAttack(pSoldier,pSoldier->sLastTarget,DONTADDTURNCOST); + + // if we can afford the minimum AP cost to use HTH combat + if (pSoldier->bActionPoints >= ubMinAPCost) + { + // then look around for a worthy target (which sets BestStab.ubPossible) + CalcBestStab(pSoldier,&BestStab, FALSE); + + if (BestStab.ubPossible && pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE) + { + // now we KNOW FOR SURE that we will do something (stab, at least) + NPCDoesAct(pSoldier); + ubBestAttackAction = AI_ACTION_KNIFE_MOVE; + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"NPC decided to move to stab"); + } + } + + // if it was in his holster, swap it back into his holster for now + if (bWeaponIn != HANDPOS) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: about to put knife away"); + RearrangePocket(pSoldier,HANDPOS,bWeaponIn,TEMPORARILY); + } + } + } + + + // copy the information on the best action selected into BestAttack struct + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"copy the information on the best action selected into BestAttack struct"); + switch (ubBestAttackAction) + { + case AI_ACTION_KNIFE_MOVE: + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = stab with a knife"); + memcpy(&BestAttack,&BestStab,sizeof(BestAttack)); + break; + + default: + // set to empty + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: best attack = no good attack"); + memset( &BestAttack, 0, sizeof( BestAttack ) ); + break; + } + } + + // NB a desire of 4 or more is only achievable by brave/aggressive guys with high morale + UINT16 usRange = GetModifiedGunRange(pSoldier->inv[ BestAttack.bWeaponIn ].usItem); + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER)"); + ////////////////////////////////////////////////////////////////////////// + // IF NECESSARY, DECIDE BETWEEN ATTACKING AND DEFENDING (TAKING COVER) + ////////////////////////////////////////////////////////////////////////// + + // if both are possible + if ((ubBestAttackAction != AI_ACTION_NONE) && ( !TileIsOutOfBounds(sBestCover))) + { + // gotta compare their merits and select the more desirable option + iOffense = BestAttack.ubChanceToReallyHit; + iDefense = iCoverPercentBetter; + + // based on how we feel about the situation, decide whether to attack first + switch (pSoldier->aiData.bAIMorale) + { + case MORALE_FEARLESS: + iOffense += iOffense / 2; // increase 50% + break; + + case MORALE_CONFIDENT: + iOffense += iOffense / 4; // increase 25% + break; + + case MORALE_NORMAL: + break; + + case MORALE_WORRIED: + iDefense += iDefense / 4; // increase 25% + break; + + case MORALE_HOPELESS: + iDefense += iDefense / 2; // increase 50% + break; + } + + + // smart guys more likely to try to stay alive, dolts more likely to shoot! + if (pSoldier->stats.bWisdom >= 50) //Madd: reduced the wisdom required to want to live... + iDefense += 10; + else if (pSoldier->stats.bWisdom < 30) + iDefense -= 10; + + // some orders are more offensive, others more defensive + if (pSoldier->aiData.bOrders == SEEKENEMY) + iOffense += 10; + else if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD) || pSoldier->aiData.bOrders == SNIPER ) + iDefense += 10; + + switch (pSoldier->aiData.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 ATTACKSLAYONLY:iOffense += 30; break; + } + + #ifdef DEBUGDECISIONS + STR tempstr=""; + sprintf( tempstr, "%s - CHOICE: iOffense = %d, iDefense = %d\n", + pSoldier->name,iOffense,iDefense); + DebugAI( tempstr ); + #endif + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: if his defensive instincts win out, forget all about the attack"); + // if his defensive instincts win out, forget all about the attack + if (iDefense > iOffense) + ubBestAttackAction = AI_ACTION_NONE; + } + + + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionBlack: is attack still desirable? ubBestAttackAction = %d",ubBestAttackAction)); + + // if attack is still desirable (meaning it's also preferred to taking cover) + if (ubBestAttackAction != AI_ACTION_NONE) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"DecideActionBlack: attack is still desirable (meaning it's also preferred to taking cover)"); + // if we wanted to be REALLY mean, we could look at chance to hit and decide whether + // to shoot at the head... + + fChangeStanceFirst = FALSE; + + // default settings + //POSSIBLE STRUCTURE CHANGE PROBLEM, NOT CURRENTLY CHANGED. GOTTHARD 7/14/08 + pSoldier->aiData.bAimTime = BestAttack.ubAimTime; + pSoldier->bDoBurst = 0; + + // HEADROCK HAM 3.6: bAimTime represents how MANY aiming levels are used, not how much APs they cost necessarily. + INT16 sActualAimTime = CalcAPCostForAiming( pSoldier, BestAttack.sTarget, (INT8)pSoldier->aiData.bAimTime ); + + if (ubBestAttackAction == AI_ACTION_KNIFE_MOVE && gGameOptions.fNewTraitSystem) + { + pSoldier->aiData.bAimTime = 0; + + if (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_PUNCH) + { + if (HAS_SKILL_TRAIT( pSoldier, MARTIAL_ARTS_NT)) + { + if( PreRandom( (gGameExternalOptions.fEnhancedCloseCombatSystem ? 2 : 0) + 2 * NUM_SKILL_TRAITS( pSoldier, MARTIAL_ARTS_NT) ) > 2 ) + { + pSoldier->aiData.bAimTime = (gGameExternalOptions.fEnhancedCloseCombatSystem ? gSkillTraitValues.ubModifierForAPsAddedOnAimedPunches : 6); + } + } + } + else + { + if (HAS_SKILL_TRAIT( pSoldier, MELEE_NT)) + { + if( PreRandom( gGameExternalOptions.fEnhancedCloseCombatSystem ? 3 : 1 ) > 0 ) + { + pSoldier->aiData.bAimTime = (gGameExternalOptions.fEnhancedCloseCombatSystem ? gSkillTraitValues.ubModifierForAPsAddedOnAimedBladedAttackes : 6); + } + } + } + } + + ////////////////////////////////////////////////////////////////////////// + // OTHERWISE, JUST GO AHEAD & ATTACK! + ////////////////////////////////////////////////////////////////////////// + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"OTHERWISE, JUST GO AHEAD & ATTACK!"); + + // swap weapon to hand if necessary + if (BestAttack.bWeaponIn != HANDPOS) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionblack: swap weapon into hand"); + RearrangePocket(pSoldier,HANDPOS,BestAttack.bWeaponIn,FOREVER); + } + + if (fChangeStanceFirst) + { // currently only for guns... + pSoldier->aiData.bNextAction = AI_ACTION_FIRE_GUN; + pSoldier->aiData.usNextActionData = BestAttack.sTarget; + pSoldier->aiData.bNextTargetLevel = BestAttack.bTargetLevel; + pSoldier->aiData.usActionData = ubBestStance; + return( AI_ACTION_CHANGE_STANCE ); + } + else + { + pSoldier->aiData.usActionData = BestAttack.sTarget; + pSoldier->bTargetLevel = BestAttack.bTargetLevel; + + #ifdef DEBUGDECISIONS + STR tempstr=""; + sprintf( 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,pSoldier->name , + BestAttack.sTarget,BestAttack.ubAimTime ); + DebugAI( tempstr); + #endif + + //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->aiData.bRTPCombat,ubBestAttackAction )); + //should be a bug + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, String("DecideActionBlack: Check for GL Bursts, is launcher capable? = %d, rtpcombat? = %d, bestattackaction = %d",IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier ),pSoldier->aiData.bRTPCombat,ubBestAttackAction )); + if (ubBestAttackAction == AI_ACTION_TOSS_PROJECTILE && (Item[pSoldier->inv[BestAttack.bWeaponIn].usItem].usItemClass == IC_LAUNCHER && IsGunBurstCapable( &pSoldier->inv[BestAttack.bWeaponIn], FALSE, pSoldier )) && + (pSoldier->bTeam != gbPlayerNum || pSoldier->aiData.bRTPCombat == RTP_COMBAT_AGGRESSIVE) ) + { + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing burst calc"); + ubBurstAPs = CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestAttack.bWeaponIn]), pSoldier ); + + if ( (pSoldier->bActionPoints - BestAttack.ubAPCost) >= ubBurstAPs ) + { + // Base chance of bursting is 25% if best shot was +0 aim, down to 8% at +4 + if ( TANK( pSoldier ) ) + { + iChance = 100; + } + else + { + iChance = 50; + switch (pSoldier->aiData.bAttitude) + { + case DEFENSIVE: iChance += -5; break; + case BRAVESOLO: iChance += 5; break; + case BRAVEAID: iChance += 5; break; + case CUNNINGSOLO: iChance += 0; break; + case CUNNINGAID: iChance += 0; break; + case AGGRESSIVE: iChance += 10; break; + case ATTACKSLAYONLY:iChance += 30; break; + } + // increase chance based on proximity and difficulty of enemy + DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("DecideActionBlack: check chance to gl burst")); + iChance += ( 15 - PythSpacesAway( pSoldier->sGridNo, BestAttack.sTarget ) ) * ( 1 + SoldierDifficultyLevel( pSoldier ) ); + } + + if ( (INT32) PreRandom( 100 ) < iChance) + { + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "DecideActionBlack: Doing GL burst"); + BestAttack.ubAimTime = BURSTING; + BestAttack.ubAPCost = BestAttack.ubAPCost + CalcAPsToBurst( pSoldier->CalcActionPoints(), &(pSoldier->inv[HANDPOS]), pSoldier ); + // check for spread burst possibilities + if (pSoldier->aiData.bAttitude != ATTACKSLAYONLY) + { + CalcSpreadBurst( pSoldier, BestAttack.sTarget, BestAttack.bTargetLevel ); + } + pSoldier->bDoBurst = 1; + + } + } + } + + + return(ubBestAttackAction); + } + + } + + // end of tank AI + if ( TANK( pSoldier ) ) + { + return( AI_ACTION_NONE ); + } + + // get the location of the closest reachable opponent + /* Flugente 22.02.2012 - A few clarifications: I changed ClosestSeenOpponent so that for zombies, this function also returns an opponent if he is on the + * roof of a building, we are not, but our GridNo belongs to that same building. + * If that is the case, it is clear that we have to get on that roof. However, we cannot do that in BlackState. If, by pure chance, we can still see our + * enemy, we cannot climb (there is no climbing option in BlackState sofar). + * So, I changed the code so that now we will climb the roof. + */ + INT32 targetGridNo = -1; + INT8 targetbLevel = 0; + sClosestOpponent = ClosestSeenOpponentforZombie(pSoldier, &targetGridNo, &targetbLevel); + if ( pSoldier->ubSoldierClass == SOLDIER_CLASS_ZOMBIE ) + { + if (!TileIsOutOfBounds(sClosestOpponent) && ! ( (targetbLevel != pSoldier->pathing.bLevel) && SameBuilding( pSoldier->sGridNo, targetGridNo ) ) ) + { + ////////////////////////////////////////////////////////////////////// + // GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT + ////////////////////////////////////////////////////////////////////// + + // try to move towards him + pSoldier->aiData.usActionData = GoAsFarAsPossibleTowards(pSoldier,sClosestOpponent,AI_ACTION_GET_CLOSER); + + // if it's possible + if (!TileIsOutOfBounds(pSoldier->aiData.usActionData)) + { + if (targetbLevel != pSoldier->pathing.bLevel && gGameExternalOptions.fZombieCanClimb ) + return(AI_ACTION_MOVE_TO_CLIMB); + else + return(AI_ACTION_SEEK_OPPONENT); + } + } + // The situation mentioned above happens... + else if ( (targetbLevel != pSoldier->pathing.bLevel) && SameBuilding( pSoldier->sGridNo, targetGridNo ) ) + { + if ( gGameExternalOptions.fZombieCanClimb ) + { + if (!TileIsOutOfBounds(targetGridNo) ) + { + // need to climb AND have enough APs to get there this turn + BOOLEAN fUp = TRUE; + if (pSoldier->pathing.bLevel > 0 ) + fUp = FALSE; + + if ( pSoldier->bActionPoints > GetAPsToClimbRoof ( pSoldier, fUp ) ) + { + pSoldier->aiData.usActionData = targetGridNo;//FindClosestClimbPoint(pSoldier, fUp ); + + // Necessary test: can we climb up at this position? It might happen that our target is directly above us, then we'll have to move + INT8 newdirection; + if ( ( fUp && FindHeigherLevel( pSoldier, pSoldier->sGridNo, pSoldier->ubDirection, &newdirection ) ) || ( !fUp && FindLowerLevel( pSoldier, pSoldier->sGridNo, pSoldier->ubDirection, &newdirection ) ) ) + { + return( AI_ACTION_CLIMB_ROOF ); + } + else + { + return(AI_ACTION_SEEK_OPPONENT); + } + } + } } } } - } - //////////////////////////////////////////////////////////////////////////// - // DO NOTHING: Not enough points left to move, so save them for next turn - //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + // TURN TO FACE CLOSEST KNOWN OPPONENT (IF NOT FACING THERE ALREADY) + //////////////////////////////////////////////////////////////////////////// -#ifdef DEBUGDECISIONS - AINameMessage(pSoldier,"- DOES NOTHING (BLACK)",1000); -#endif - - // by default, if everything else fails, just stand in place and wait - pSoldier->aiData.usActionData = NOWHERE; - return(AI_ACTION_NONE); - -} - -INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier ) -{ - INT8 bAction = AI_ACTION_NONE; - - switch (pSoldier->aiData.bAlertStatus) - { - case STATUS_GREEN: - #ifdef DEBUGDECISIONS - AIPopMessage("AlertStatus = GREEN"); - #endif - bAction = ZombieDecideActionGreen(pSoldier); - break; - - case STATUS_YELLOW: - #ifdef DEBUGDECISIONS - AIPopMessage("AlertStatus = YELLOW"); - #endif - bAction = ZombieDecideActionYellow(pSoldier); - break; - - case STATUS_RED: - #ifdef DEBUGDECISIONS - AIPopMessage("AlertStatus = RED"); - #endif - bAction = ZombieDecideActionRed(pSoldier, TRUE); - break; - - case STATUS_BLACK: - #ifdef DEBUGDECISIONS - AIPopMessage("AlertStatus = BLACK"); - #endif - bAction = ZombieDecideActionBlack(pSoldier); - break; - } - -#ifdef DEBUGDECISIONS - STR tempstr; - sprintf( tempstr, "ZombieDecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->aiData.usActionData ); - DebugAI(tempstr); -#endif - - return(bAction); -} - -void ZombieDecideAlertStatus( SOLDIERTYPE *pSoldier ) -{ - INT8 bOldStatus; - INT32 iDummy; - BOOLEAN fClimbDummy, fReachableDummy; - - // THE FOUR (4) POSSIBLE ALERT STATUSES ARE: - // GREEN - No one sensed, no suspicious noise heard, go about doing regular stuff - // YELLOW - Suspicious noise was heard personally - // RED - Either saw OPPONENTS in person, or definite contact had been called - // BLACK - Currently has one or more OPPONENTS in sight - - // save the man's previous status - bOldStatus = pSoldier->aiData.bAlertStatus; - - // determine the current alert status for this category of man - if (pSoldier->aiData.bOppCnt > 0) // opponent(s) in sight - { - pSoldier->aiData.bAlertStatus = STATUS_BLACK; - } - else // no opponents are in sight - { - switch (bOldStatus) + if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) { - case STATUS_BLACK: - // then drop back to RED status - pSoldier->aiData.bAlertStatus = STATUS_RED; - break; + // hopeless guys shouldn't waste their time this way, UNLESS they CAN move + // but chose not to to get this far (which probably means they're cornered) + // ALSO, don't bother turning if we're already aiming a gun + if ( !gfHiddenInterrupt && ((pSoldier->aiData.bAIMorale > MORALE_HOPELESS) || ubCanMove) && !AimingGun(pSoldier)) + { + // determine the location of the known closest opponent + // (don't care if he's conscious, don't care if he's reachable at all) - case STATUS_RED: - // RED can never go back down below RED, only up to BLACK - // TODO: perhaps zombies might forget their target? They are pretty dumb after all... + + sClosestOpponent = ClosestSeenOpponent(pSoldier, NULL, NULL); + // if we have a closest reachable opponent + if (!TileIsOutOfBounds(sClosestOpponent)) + { + bDirection = atan8(CenterX(pSoldier->sGridNo),CenterY(pSoldier->sGridNo),CenterX(sClosestOpponent),CenterY(sClosestOpponent)); + + // if we're not facing towards him + if ( pSoldier->ubDirection != bDirection && pSoldier->InternalIsValidStance( bDirection, gAnimControl[ pSoldier->usAnimState ].ubEndHeight ) ) + { + pSoldier->aiData.usActionData = bDirection; + + #ifdef DEBUGDECISIONS + sprintf(tempstr,"%s - TURNS to face CLOSEST OPPONENT in direction %d",pSoldier->name,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); + #endif + + return(AI_ACTION_CHANGE_FACING); + } + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // DO NOTHING: Not enough points left to move, so save them for next turn + //////////////////////////////////////////////////////////////////////////// + + #ifdef DEBUGDECISIONS + AINameMessage(pSoldier,"- DOES NOTHING (BLACK)",1000); + #endif + + // by default, if everything else fails, just stand in place and wait + pSoldier->aiData.usActionData = NOWHERE; + return(AI_ACTION_NONE); + + } + + INT8 ZombieDecideAction( SOLDIERTYPE *pSoldier ) + { + INT8 bAction = AI_ACTION_NONE; + + switch (pSoldier->aiData.bAlertStatus) + { + case STATUS_GREEN: + #ifdef DEBUGDECISIONS + AIPopMessage("AlertStatus = GREEN"); + #endif + bAction = ZombieDecideActionGreen(pSoldier); break; case STATUS_YELLOW: - // if all enemies have been RED alerted, or we're under fire - if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire) - { + #ifdef DEBUGDECISIONS + AIPopMessage("AlertStatus = YELLOW"); + #endif + bAction = ZombieDecideActionYellow(pSoldier); + break; + + case STATUS_RED: + #ifdef DEBUGDECISIONS + AIPopMessage("AlertStatus = RED"); + #endif + bAction = ZombieDecideActionRed(pSoldier, TRUE); + break; + + case STATUS_BLACK: + #ifdef DEBUGDECISIONS + AIPopMessage("AlertStatus = BLACK"); + #endif + bAction = ZombieDecideActionBlack(pSoldier); + break; + } + + #ifdef DEBUGDECISIONS + STR tempstr; + sprintf( tempstr, "ZombieDecideAction: selected action %d, actionData %d\n\n",bAction,pSoldier->aiData.usActionData ); + DebugAI(tempstr); + #endif + + return(bAction); + } + + void ZombieDecideAlertStatus( SOLDIERTYPE *pSoldier ) + { + INT8 bOldStatus; + INT32 iDummy; + BOOLEAN fClimbDummy, fReachableDummy; + + // THE FOUR (4) POSSIBLE ALERT STATUSES ARE: + // GREEN - No one sensed, no suspicious noise heard, go about doing regular stuff + // YELLOW - Suspicious noise was heard personally + // RED - Either saw OPPONENTS in person, or definite contact had been called + // BLACK - Currently has one or more OPPONENTS in sight + + // save the man's previous status + bOldStatus = pSoldier->aiData.bAlertStatus; + + // determine the current alert status for this category of man + if (pSoldier->aiData.bOppCnt > 0) // opponent(s) in sight + { + pSoldier->aiData.bAlertStatus = STATUS_BLACK; + } + else // no opponents are in sight + { + switch (bOldStatus) + { + case STATUS_BLACK: + // then drop back to RED status pSoldier->aiData.bAlertStatus = STATUS_RED; - } - else + break; + + case STATUS_RED: + // RED can never go back down below RED, only up to BLACK + // TODO: perhaps zombies might forget their target? They are pretty dumb after all... + break; + + case STATUS_YELLOW: + // if all enemies have been RED alerted, or we're under fire + if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire) + { + pSoldier->aiData.bAlertStatus = STATUS_RED; + } + else + { + // if we are NOT aware of any uninvestigated noises right now + // and we are not currently in the middle of an action + // (could still be on his way heading to investigate a noise!) + if (( TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) && !pSoldier->aiData.bActionInProgress) + { + // then drop back to GREEN status + pSoldier->aiData.bAlertStatus = STATUS_GREEN; + } + } + break; + + case STATUS_GREEN: + // if all enemies have been RED alerted, or we're under fire + if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire) + { + pSoldier->aiData.bAlertStatus = STATUS_RED; + } + else + { + // if we ARE aware of any uninvestigated noises right now + if ( !TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) + { + // then move up to YELLOW status + pSoldier->aiData.bAlertStatus = STATUS_YELLOW; + } + } + break; + } + // otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN + } + + // if the creatures alert status has changed in any way + if (pSoldier->aiData.bAlertStatus != bOldStatus) + { + // HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK + // if either status is < RED (ie. anything but RED->BLACK && BLACK->RED) + if ((bOldStatus < STATUS_RED) || (pSoldier->aiData.bAlertStatus < STATUS_RED)) + { + // force a NEW action decision on next pass through HandleManAI() + SetNewSituation( pSoldier ); + } + + // if this guy JUST discovered that there were opponents here for sure... + if ((bOldStatus < STATUS_RED) && (pSoldier->aiData.bAlertStatus >= STATUS_RED)) + { + // might want to make custom to let them go anywhere + CheckForChangingOrders(pSoldier); + } + + #ifdef DEBUGDECISIONS + // don't report status changes for human-controlled mercs + // if (!pSoldier->human) + // { + STR16 tempstr; + sprintf(tempstr,"%s's Alert Status changed from %d to %d", + pSoldier->name ,bOldStatus,pSoldier->aiData.bAlertStatus); + AIPopMessage(tempstr); + // } + #endif + + } + else // status didn't change + { + // if a guy on status GREEN or YELLOW is running low on breath + if (((pSoldier->aiData.bAlertStatus == STATUS_GREEN) && (pSoldier->bBreath < 75)) || + ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50))) + { + // as long as he's not in water (standing on a bridge is OK) + if (!pSoldier->MercInWater()) { - // if we are NOT aware of any uninvestigated noises right now - // and we are not currently in the middle of an action - // (could still be on his way heading to investigate a noise!) - if (( TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) && !pSoldier->aiData.bActionInProgress) + // force a NEW decision so that he can get some rest + SetNewSituation( pSoldier ); + + // current action will be canceled. if noise is no longer important + if ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && + (TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy)))) { // then drop back to GREEN status pSoldier->aiData.bAlertStatus = STATUS_GREEN; + CheckForChangingOrders(pSoldier); } } - break; - - case STATUS_GREEN: - // if all enemies have been RED alerted, or we're under fire - if (gTacticalStatus.Team[pSoldier->bTeam].bAwareOfOpposition || pSoldier->aiData.bUnderFire) - { - pSoldier->aiData.bAlertStatus = STATUS_RED; - } - else - { - // if we ARE aware of any uninvestigated noises right now - if ( !TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy))) - { - // then move up to YELLOW status - pSoldier->aiData.bAlertStatus = STATUS_YELLOW; - } - } - break; } - // otherwise, RED stays RED, YELLOW stays YELLOW, GREEN stays GREEN + } } - - // if the creatures alert status has changed in any way - if (pSoldier->aiData.bAlertStatus != bOldStatus) - { - // HERE ARE TRYING TO AVOID NPCs SHUFFLING BACK & FORTH BETWEEN RED & BLACK - // if either status is < RED (ie. anything but RED->BLACK && BLACK->RED) - if ((bOldStatus < STATUS_RED) || (pSoldier->aiData.bAlertStatus < STATUS_RED)) - { - // force a NEW action decision on next pass through HandleManAI() - SetNewSituation( pSoldier ); - } - - // if this guy JUST discovered that there were opponents here for sure... - if ((bOldStatus < STATUS_RED) && (pSoldier->aiData.bAlertStatus >= STATUS_RED)) - { - // might want to make custom to let them go anywhere - CheckForChangingOrders(pSoldier); - } - -#ifdef DEBUGDECISIONS - // don't report status changes for human-controlled mercs -// if (!pSoldier->human) -// { - STR16 tempstr; - sprintf(tempstr,"%s's Alert Status changed from %d to %d", - pSoldier->name ,bOldStatus,pSoldier->aiData.bAlertStatus); - AIPopMessage(tempstr); -// } #endif - - } - else // status didn't change - { - // if a guy on status GREEN or YELLOW is running low on breath - if (((pSoldier->aiData.bAlertStatus == STATUS_GREEN) && (pSoldier->bBreath < 75)) || - ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && (pSoldier->bBreath < 50))) - { - // as long as he's not in water (standing on a bridge is OK) - if (!pSoldier->MercInWater()) - { - // force a NEW decision so that he can get some rest - SetNewSituation( pSoldier ); - - // current action will be canceled. if noise is no longer important - if ((pSoldier->aiData.bAlertStatus == STATUS_YELLOW) && - (TileIsOutOfBounds(MostImportantNoiseHeard(pSoldier,&iDummy,&fClimbDummy,&fReachableDummy)))) - { - // then drop back to GREEN status - pSoldier->aiData.bAlertStatus = STATUS_GREEN; - CheckForChangingOrders(pSoldier); - } - } - } - } -} diff --git a/TacticalAI/Realtime.cpp b/TacticalAI/Realtime.cpp index a791dd6f..1711b1dd 100644 --- a/TacticalAI/Realtime.cpp +++ b/TacticalAI/Realtime.cpp @@ -27,10 +27,12 @@ INT8 RTPlayerDecideAction( SOLDIERTYPE * pSoldier ) { bAction = DecideAutoBandage( pSoldier ); } +#ifdef ENABLE_ZOMBIES else if ( pSoldier->IsZombie() ) { bAction = ZombieDecideAction( pSoldier ); } +#endif else { bAction = DecideAction( pSoldier ); @@ -51,10 +53,12 @@ INT8 RTDecideAction(SOLDIERTYPE *pSoldier) { return( CreatureDecideAction( pSoldier ) ); } +#ifdef ENABLE_ZOMBIES else if ( pSoldier->IsZombie() ) { return( ZombieDecideAction( pSoldier ) ); } +#endif else if (pSoldier->ubBodyType == CROW) { return( CrowDecideAction( pSoldier ) ); diff --git a/TileEngine/Explosion Control.cpp b/TileEngine/Explosion Control.cpp index ff45c19b..df978ee8 100644 --- a/TileEngine/Explosion Control.cpp +++ b/TileEngine/Explosion Control.cpp @@ -1582,8 +1582,25 @@ BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT32 sBombGridNo sNewWoundAmt = max( 1, (INT16)(sNewWoundAmt * (100 - gSkillTraitValues.ubBBDamageResistance) / 100)); // Flugente: moved the damage calculation into a separate function +#ifdef ENABLE_ZOMBIES if ( pSoldier->GetDamageResistance(FALSE, FALSE) > 0 ) sNewWoundAmt -= ((sNewWoundAmt * pSoldier->GetDamageResistance(FALSE, FALSE)) /100); +#else + // Damage resistance for Militia + if (pSoldier->ubSoldierClass == SOLDIER_CLASS_GREEN_MILITIA && gGameExternalOptions.bGreenMilitiaDamageResistance != 0) + sNewWoundAmt -= ((sNewWoundAmt * gGameExternalOptions.bGreenMilitiaDamageResistance) /100); + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_REG_MILITIA && gGameExternalOptions.bRegularMilitiaDamageResistance != 0) + sNewWoundAmt -= ((sNewWoundAmt * gGameExternalOptions.bRegularMilitiaDamageResistance) /100); + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE_MILITIA && gGameExternalOptions.bVeteranMilitiaDamageResistance != 0) + sNewWoundAmt -= ((sNewWoundAmt * gGameExternalOptions.bVeteranMilitiaDamageResistance) /100); + // bonus for enemy too + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ADMINISTRATOR && gGameExternalOptions.sEnemyAdminDamageResistance != 0) + sNewWoundAmt -= ((sNewWoundAmt * gGameExternalOptions.sEnemyAdminDamageResistance) /100); + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ARMY && gGameExternalOptions.sEnemyRegularDamageResistance != 0) + sNewWoundAmt -= ((sNewWoundAmt * gGameExternalOptions.sEnemyRegularDamageResistance) /100); + else if (pSoldier->ubSoldierClass == SOLDIER_CLASS_ELITE && gGameExternalOptions.sEnemyEliteDamageResistance != 0) + sNewWoundAmt -= ((sNewWoundAmt * gGameExternalOptions.sEnemyEliteDamageResistance) /100); +#endif // we can loose stats due to being hit by the blast else if ( gGameOptions.fNewTraitSystem && Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL && @@ -1782,6 +1799,38 @@ BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT32 sBombGridNo sNewWoundAmt = max(1, sNewWoundAmt); } ////////////////////////////////////////////////////////////////////////////////////// + +#ifdef ENABLE_ZOMBIES + // Nothing to do here +#else + //////////////////////////////////////////////////////////////////////////////////// + // SANDRO - option to make special NPCs stronger - damage resistance + if (gGameExternalOptions.usSpecialNPCStronger > 0) + { + switch( pSoldier->ubProfile ) + { + case CARMEN: + case QUEEN: + case JOE: + case ANNIE: + case CHRIS: + case KINGPIN: + case TIFFANY: + case T_REX: + case DRUGGIST: + case GENERAL: + case JACK: + case OLAF: + case RAY: + case OLGA: + case TYRONE: + case MIKE: + sNewWoundAmt -= (sNewWoundAmt * gGameExternalOptions.usSpecialNPCStronger / 200); + sNewWoundAmt = max( 1, sNewWoundAmt); + break; + } + } +#endif // SANDRO - new merc records - times wounded (blasted by explosion) if ( ( sNewWoundAmt > 1 || sBreathAmt > 1000 ) && pSoldier->ubProfile != NO_PROFILE ) @@ -1882,7 +1931,11 @@ BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, IN if ( pExplosive->ubType == EXPLOSV_TEARGAS ) { // zombies and robots are unaffected by tear gas +#ifdef ENABLE_ZOMBIES if ( AM_A_ROBOT( pSoldier ) || pSoldier->IsZombie() ) +#else + if ( AM_A_ROBOT( pSoldier ) ) +#endif { return( fRecompileMovementCosts ); } @@ -1896,8 +1949,12 @@ BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, IN } else if ( pExplosive->ubType == EXPLOSV_MUSTGAS ) { +#ifdef ENABLE_ZOMBIES // zombies and robots are unaffected by mustard gas if ( AM_A_ROBOT( pSoldier ) || pSoldier->IsZombie() ) +#else + if ( AM_A_ROBOT( pSoldier ) ) +#endif { return( fRecompileMovementCosts ); } diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 3949cc18..f7f319ff 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -5115,7 +5115,10 @@ STR16 zOptionsToggleText[] = L"自动加速敌军回合", // Automatic fast forward through AI turns #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // TODO.Translate +#endif + L"--作弊模式选项--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"强制 Bobby Ray 送货", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5228,7 +5231,10 @@ STR16 zOptionsScreenHelpText[] = L"打开时,敌军回合将被大幅加速。", #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"强制 Bobby Ray 出货", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index 62dafd81..ef177826 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -5114,7 +5114,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // TODO.Translate +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5227,7 +5230,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index ac63fe7a..1b355ccb 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -5113,7 +5113,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5226,7 +5229,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will spawn. Be aware!", // allow zombies +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index a96fc380..261ed121 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -5104,7 +5104,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Forcer envois Bobby Ray", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5217,7 +5220,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Forcer tous les envois en attente de Bobby Ray", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index 69a8a739..077807cd 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -4962,7 +4962,10 @@ STR16 zOptionsToggleText[] = L"Autom. schnelle Gegner-Züge", // Automatic fast forward through AI turns #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Erzwinge BR Lieferung", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5075,7 +5078,10 @@ STR16 zOptionsScreenHelpText[] = L"Wenn diese Funktion aktiviert ist, werden gegnerische Züge schneller durchgeführt.", #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index da02cf1b..24a1c6d2 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -5099,7 +5099,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5212,7 +5215,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 336b67d5..54366931 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -5111,7 +5111,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5224,7 +5227,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"WymuŚ wszystkie oczekiwane dostawy od Bobby Ray's.", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index 6779f607..0a5cd764 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -5094,7 +5094,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Читерские настройки--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Ускорить доставку Бобби Рэя", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5207,7 +5210,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Выберите этот пункт чтобы груз Бобби Рэя прибыл немедленно.", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/Utils/_TaiwaneseText.cpp b/Utils/_TaiwaneseText.cpp index ec73b153..c9e7cf50 100644 --- a/Utils/_TaiwaneseText.cpp +++ b/Utils/_TaiwaneseText.cpp @@ -5115,7 +5115,10 @@ STR16 zOptionsToggleText[] = L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"Allow Zombies", // Flugente Zombies 1.0 +#endif + L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -5228,7 +5231,10 @@ STR16 zOptionsScreenHelpText[] = L"When ON, AI turns will be much faster.", // TODO.Translate #endif +#ifdef ENABLE_ZOMBIES L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate +#endif + L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", diff --git a/builddefines.h b/builddefines.h index a8ccd5c5..01c2d7f7 100644 --- a/builddefines.h +++ b/builddefines.h @@ -3,10 +3,22 @@ #include "Language Defines.h" +// ----------------------------- +// ***************************** +// WANNE: Enable this define, to build an executable where you can enable/disable zombies in tactical (ingame option screen). +// It seems, that executables compiled with this zombie option turned on, causes slowdown in tactical. +//#define ENABLE_ZOMBIES +// ----------------------------- +// ***************************** + +// ----------------------------- +// ***************************** // WANNE: Enable this define, to build the gameloop with the highspeed timer. To use the highspeed timer, you also have to set the HIGHSPEED_TIMER = TRUE in ja2.ini // CAUTION: A lot of games reported, when the exe is compiled with the highspeed timer on, the game runs very slowly in strategy map screen when doing repair, .. // This is the reason, why I made a compilation define, where we can turn on/off the highspeed timer. -//#define USE_HIGHSPEED_GAMELOOP_TIMER +#define USE_HIGHSPEED_GAMELOOP_TIMER +// ----------------------------- +// ***************************** // ----------------------------- // *****************************