From c90f4bce475d56afd086d8d974464f211aa7e138 Mon Sep 17 00:00:00 2001 From: Wanne Date: Tue, 29 May 2012 22:49:17 +0000 Subject: [PATCH] - New feature: zombies can now spawn from corpses, if you specify that option in the preferences screen (by Flugente) o if set to off, no zombies will spawn. This can also be changed in mid-game o for more information on how this works and how to set it up, see http://www.bears-pit.com/board/ubbthreads.php/topics/295746/Zombies_WH40K_and_more.html#Post295746 o with the tag , you can now poison guns or ammo. This is the percentage of damage dealt that will be poisonous (see zombie thread for an explanation) o The zombie option can only be enabled in a single player game o AI fix: to prevent the endless clock problem, the AI now ends the turn if the same actor calls the decision routine over 100 times in a single turn. git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5313 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Editor/EditorMercs.cpp | 4 + GameSettings.cpp | 30 +- GameSettings.h | 21 +- GameVersion.h | 3 +- Laptop/personnel.cpp | 12 +- Multiplayer/client.cpp | 4 +- Options Screen.cpp | Bin 129016 -> 129020 bytes SaveLoadGame.cpp | 9 + Strategic/Assignments.cpp | 236 ++- Strategic/Auto Resolve.cpp | 68 +- Strategic/Facilities.cpp | 2 +- Strategic/Map Screen Interface.cpp | 49 +- Strategic/Merc Contract.cpp | 4 + Strategic/Queen Command.cpp | 10 + Tactical/Auto Bandage.cpp | 28 +- Tactical/Civ Quotes.cpp | 4 + Tactical/Drugs And Alcohol.cpp | 5 +- Tactical/Handle Items.cpp | 2 +- Tactical/Interface Control.cpp | 18 +- Tactical/Interface Dialogue.cpp | 4 + Tactical/Interface Enhanced.cpp | 400 ++--- Tactical/Interface Panels.cpp | 36 +- Tactical/Interface Utils.cpp | 101 +- Tactical/Interface.cpp | 69 + Tactical/Inventory Choosing.cpp | 7 + Tactical/Item Types.h | 6 + Tactical/Items.cpp | 59 +- Tactical/Items.h | 6 +- Tactical/Keys.cpp | 4 +- Tactical/LOS.cpp | 1 + Tactical/Militia Control.cpp | 16 - Tactical/Morale.cpp | 10 +- Tactical/Morale.h | 3 + Tactical/Overhead.cpp | 34 +- Tactical/Overhead.h | 3 + Tactical/Rotting Corpses.cpp | 398 +++++ Tactical/Rotting Corpses.h | 59 +- Tactical/SkillCheck.cpp | 20 +- Tactical/SkillCheck.h | 1 + Tactical/Soldier Ani.cpp | 3 + Tactical/Soldier Control.cpp | 565 +++++-- Tactical/Soldier Control.h | 37 +- Tactical/Soldier Create.cpp | 56 +- Tactical/Soldier Create.h | 4 + Tactical/Soldier Profile.cpp | 1 + Tactical/Tactical Turns.cpp | 5 + Tactical/Turn Based Input.cpp | 4 + Tactical/Vehicles.cpp | 4 +- Tactical/Weapons.cpp | 77 +- Tactical/Weapons.h | 3 + Tactical/World Items.cpp | 8 +- Tactical/XML_AmmoTypes.cpp | 13 +- Tactical/opplist.cpp | 1 + Tactical/soldier profile type.h | 1 + TacticalAI/AIInternals.h | 8 + TacticalAI/AIMain.cpp | 32 +- TacticalAI/AIUtils.cpp | 98 ++ TacticalAI/CreatureDecideAction.cpp | 1 + TacticalAI/DecideAction.cpp | 2174 +++++++++++++++++++++++++ TacticalAI/Realtime.cpp | 8 + TileEngine/Explosion Control.cpp | 65 +- TileEngine/Tactical Placement GUI.cpp | 31 +- Utils/Text.h | 11 +- Utils/XML_Items.cpp | 11 + Utils/_ChineseText.cpp | 15 +- Utils/_DutchText.cpp | 13 + Utils/_EnglishText.cpp | 18 +- Utils/_FrenchText.cpp | 13 + Utils/_GermanText.cpp | 12 + Utils/_ItalianText.cpp | 13 + Utils/_PolishText.cpp | 13 + Utils/_RussianText.cpp | 13 + Utils/_TaiwaneseText.cpp | 13 + 73 files changed, 4481 insertions(+), 609 deletions(-) diff --git a/Editor/EditorMercs.cpp b/Editor/EditorMercs.cpp index 0ccfd17d..0e188bd9 100644 --- a/Editor/EditorMercs.cpp +++ b/Editor/EditorMercs.cpp @@ -792,6 +792,10 @@ void ChangeBaseSoldierStats( SOLDIERTYPE *pSoldier ) pSoldier->bBleeding = 0; pSoldier->bBreath = 100; + pSoldier->bPoisonSum = 0; + pSoldier->bPoisonLife = 0; + pSoldier->bPoisonBleeding = 0; + pSoldier->stats.bMarksmanship = (UINT8)(sBaseStat[sCurBaseDiff] + (UINT16)(Random(BASE_STAT_DEVIATION * 2)-BASE_STAT_DEVIATION)); pSoldier->stats.bMedical = (UINT8)(sBaseStat[sCurBaseDiff] + (UINT16)(Random(BASE_STAT_DEVIATION * 2)-BASE_STAT_DEVIATION)); pSoldier->stats.bMechanical = (UINT8)(sBaseStat[sCurBaseDiff] + (UINT16)(Random(BASE_STAT_DEVIATION * 2)-BASE_STAT_DEVIATION)); diff --git a/GameSettings.cpp b/GameSettings.cpp index 182e6c6a..235a6dc7 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -241,7 +241,7 @@ BOOLEAN LoadGameSettings() if (!IsHiSpeedClockMode()) gGameSettings.fOptions[TOPTION_AUTO_FAST_FORWARD_MODE] = FALSE; - //gGameOptions.fWeaponOverheating = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_WEAPON_OVERHEATING" , FALSE ); // Flugente FTW 1: Weapon Overheating + gGameSettings.fOptions[TOPTION_ZOMBIES] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_ZOMBIES" , FALSE ); gGameSettings.fOptions[NUM_ALL_GAME_OPTIONS] = iniReader.ReadBoolean("JA2 Game Settings","NUM_ALL_GAME_OPTIONS" , FALSE ); @@ -341,8 +341,6 @@ BOOLEAN SaveGameSettings() settings << "; UINT8 fOptions[ NUM_ALL_GAME_OPTIONS ]; // Toggle Options (Speech, Subtitles, Show Tree Tops, etc.. )" << endl; settings << "; UINT32 uiMeanwhileScenesSeenFlags; // Bit Vector describing seen 'mean whiles..'" << endl; settings << "; BOOLEAN fHideHelpInAllScreens; // Controls Help \"do not show help again\" checkbox" << endl; - //settings << "; UINT8 ubSizeOfDisplayCover; // The number of grids the player designates thru [Delete + ( = or - )]" << endl; - //settings << "; UINT8 ubSizeOfLOS; // The number of grids the player designates thru [End + ( = or - )]" << endl; settings << "; } GAME_SETTINGS" << endl; settings << ";" << endl; settings << ";******************************************************************************************************************************" << endl; @@ -355,8 +353,6 @@ BOOLEAN SaveGameSettings() settings << "ubSpeechVolume = " << (int)gGameSettings.ubSpeechVolume << endl; settings << "uiMeanwhileScenesSeenFlags = " << gGameSettings.uiMeanwhileScenesSeenFlags << endl; settings << "fHideHelpInAllScreens = " << (gGameSettings.fHideHelpInAllScreens ? "TRUE" : "FALSE" ) << endl; - //settings << "ubSizeOfDisplayCover = " << (int)gGameSettings.ubSizeOfDisplayCover << endl; - //settings << "ubSizeOfLOS = " << (int)gGameSettings.ubSizeOfLOS << endl; settings << "TOPTION_SPEECH = " << (gGameSettings.fOptions[TOPTION_SPEECH] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_MUTE_CONFIRMATIONS = " << (gGameSettings.fOptions[TOPTION_MUTE_CONFIRMATIONS] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_SUBTITLES = " << (gGameSettings.fOptions[TOPTION_SUBTITLES] ? "TRUE" : "FALSE" ) << endl; @@ -391,20 +387,18 @@ BOOLEAN SaveGameSettings() settings << "TOPTION_ALLOW_SOLDIER_TOOLTIPS = " << (gGameSettings.fOptions[TOPTION_ALLOW_SOLDIER_TOOLTIPS] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_USE_AUTO_SAVE = " << (gGameSettings.fOptions[TOPTION_USE_AUTO_SAVE] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_SILENT_SKYRIDER = " << (gGameSettings.fOptions[TOPTION_SILENT_SKYRIDER] ? "TRUE" : "FALSE" ) << endl; - //settings << "TOPTION_LOW_CPU_USAGE = " << (gGameSettings.fOptions[TOPTION_LOW_CPU_USAGE] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_ENHANCED_DESC_BOX = " << (gGameSettings.fOptions[TOPTION_ENHANCED_DESC_BOX] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_TOGGLE_TURN_MODE = " << (gGameSettings.fOptions[TOPTION_TOGGLE_TURN_MODE] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_STAT_PROGRESS_BARS = " << (gGameSettings.fOptions[TOPTION_STAT_PROGRESS_BARS] ? "TRUE" : "FALSE" ) << endl; // HEADROCK HAM 3.6: Progress Bars settings << "TOPTION_ALT_MAP_COLOR = " << (gGameSettings.fOptions[TOPTION_ALT_MAP_COLOR] ? "TRUE" : "FALSE" ) << endl; // HEADROCK HAM 4: Alt Map Colors settings << "TOPTION_ALTERNATE_BULLET_GRAPHICS = " << (gGameSettings.fOptions[TOPTION_ALTERNATE_BULLET_GRAPHICS] ? "TRUE" : "FALSE" ) << endl; - //settings << "TOPTION_USE_NCTH = " << (gGameSettings.fOptions[TOPTION_USE_NCTH] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_SHOW_MERC_RANKS = " << (gGameSettings.fOptions[TOPTION_SHOW_MERC_RANKS] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_SHOW_TACTICAL_FACE_GEAR = " << (gGameSettings.fOptions[TOPTION_SHOW_TACTICAL_FACE_GEAR] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_SHOW_TACTICAL_FACE_ICONS = " << (gGameSettings.fOptions[TOPTION_SHOW_TACTICAL_FACE_ICONS] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_DISABLE_CURSOR_SWAP = " << (gGameSettings.fOptions[TOPTION_DISABLE_CURSOR_SWAP] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_QUIET_TRAINING = " << (gGameSettings.fOptions[TOPTION_QUIET_TRAINING] ? "TRUE" : "FALSE" ) << endl; settings << "TOPTION_AUTO_FAST_FORWARD_MODE = " << (gGameSettings.fOptions[TOPTION_AUTO_FAST_FORWARD_MODE] ? "TRUE" : "FALSE" ) << endl; - //settings << "TOPTION_WEAPON_OVERHEATING = " << (gGameOptions.fWeaponOverheating ? "TRUE" : "FALSE" ) << endl; // Flugente FTW 1: Weapon Overheating + settings << "TOPTION_ZOMBIES = " << (gGameSettings.fOptions[TOPTION_ZOMBIES] ? "TRUE" : "FALSE" ) << endl; 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; @@ -530,9 +524,8 @@ void InitGameSettings() gGameSettings.fOptions[ TOPTION_DISABLE_CURSOR_SWAP ] = FALSE; gGameSettings.fOptions[ TOPTION_QUIET_TRAINING ] = FALSE; gGameSettings.fOptions[ TOPTION_AUTO_FAST_FORWARD_MODE ] = FALSE; + gGameSettings.fOptions[ TOPTION_ZOMBIES ] = FALSE; // Flugente Zombies 1.0 - // Flugente FTW 1: Weapon Overheating - //gGameSettings.fOptions[ TOPTION_WEAPON_OVERHEATING ] = TRUE; // arynn: Cheat/Debug Menu gGameSettings.fOptions[ TOPTION_CHEAT_MODE_OPTIONS_HEADER ] = FALSE; @@ -547,6 +540,8 @@ void InitGameSettings() gGameSettings.fOptions[ TOPTION_RENDER_MOUSE_REGIONS ] = FALSE; // an example of a DEBUG build option gGameSettings.fOptions[ TOPTION_DEBUG_MODE_OPTIONS_END ] = FALSE; // an example options screen options divider (pure text) + + // enum control options (not real options but included here for the sake of complete control of values) //gGameSettings.ubSizeOfDisplayCover = 16; @@ -1331,6 +1326,21 @@ 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); + //################# 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); + gGameExternalOptions.sZombieRiseWaveFrequency = iniReader.ReadInteger("Tactical Zombie Settings", "ZOMBIE_RISE_WAVE_FREQUENCY", 30, 0, 100); + gGameExternalOptions.fZombieCanClimb = iniReader.ReadBoolean("Tactical Zombie Settings", "ZOMBIE_CAN_CLIMB", TRUE); + gGameExternalOptions.fZombieExplodingCivs = iniReader.ReadBoolean("Tactical Zombie Settings", "ZOMBIE_EXPLODING_CIVS", FALSE); + gGameExternalOptions.sEnemyZombieDamageResistance = iniReader.ReadInteger("Tactical Zombie Settings", "ZOMBIE_DAMAGE_RESISTANCE", 0, -50, 95); + 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); + + //################# Tactical Poison Settings ################## + gGameExternalOptions.ubPoisonBaseMedicalSkillToCure = iniReader.ReadInteger("Tactical Poison Settings", "POISON_BASE_MEDICAL_SKILL_TO_CURE", 50, 1, 100); + gGameExternalOptions.sZombiePoisonDamagePercentage = iniReader.ReadInteger("Tactical Poison Settings", "ZOMBIE_POISON_DAMAGE_PERCENTAGE", 50, 0, 100); + gGameExternalOptions.sPoisonInfectionDamageMultiplier = iniReader.ReadFloat("Tactical Poison Settings", "POISON_INFECTION_DAMAGE_MULTIPLIER", 4.0, 1.0, 10.0); + //################# Strategic Gamestart Settings ################## //Lalien: Game starting time diff --git a/GameSettings.h b/GameSettings.h index 41155fa8..84f92fff 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -89,9 +89,7 @@ enum TOPTION_DISABLE_CURSOR_SWAP, // Disable cursor swapping every second between talk and quick exchange TOPTION_QUIET_TRAINING, //Madd: mercs don't say gained experience quote while training TOPTION_AUTO_FAST_FORWARD_MODE, // automatically fast forward through AI turns - - // Flugente FTW 1: Weapon Overheating - //TOPTION_WEAPON_OVERHEATING, // allow Overheating of weapons + TOPTION_ZOMBIES, // Flugente Zombies 1.0: allow zombies // arynn: Debug/Cheat TOPTION_CHEAT_MODE_OPTIONS_HEADER, @@ -107,7 +105,7 @@ enum TOPTION_RETAIN_DEBUG_OPTIONS_IN_RELEASE, // allow debug options that were set in debug.exe to continue in a rel.exe (debugging release can be beneficial) TOPTION_DEBUG_MODE_RENDER_OPTIONS_GROUP, // an example option that will show/hide other options TOPTION_RENDER_MOUSE_REGIONS, // an example of a DEBUG build option - TOPTION_DEBUG_MODE_OPTIONS_END, // an example options screen options divider (pure text) + TOPTION_DEBUG_MODE_OPTIONS_END, // an example options screen options divider (pure text) // this is THE LAST option that exists (intended for debugging the options screen, doesnt do anything, except exist) TOPTION_LAST_OPTION, @@ -365,6 +363,21 @@ typedef struct //Video settings BOOLEAN gfVSync; + // Flugente: zombie settings + INT8 sZombieRiseBehaviour; + BOOLEAN fZombieSpawnWaves; + INT8 sZombieRiseWaveFrequency; + BOOLEAN fZombieCanClimb; + BOOLEAN fZombieExplodingCivs; + INT8 sEnemyZombieDamageResistance; + BOOLEAN fZombieOnlyHeadshotsWork; + INT8 sZombieDifficultyLevel; + + // Flugente: poison settings + INT32 ubPoisonBaseMedicalSkillToCure; + INT16 sZombiePoisonDamagePercentage; + FLOAT sPoisonInfectionDamageMultiplier; + //Animation settings FLOAT giPlayerTurnSpeedUpFactor; FLOAT giEnemyTurnSpeedUpFactor; diff --git a/GameVersion.h b/GameVersion.h index 5b7e232d..c181a058 100644 --- a/GameVersion.h +++ b/GameVersion.h @@ -20,8 +20,9 @@ extern CHAR16 zTrackingNumber[16]; // // Keeps track of the saved game version. Increment the saved game version whenever // you will invalidate the saved game file -#define SAVE_GAME_VERSION 131 //125 //124 //114 //113 //112 //111 //110 //109 //108 //107 //106 //105 //104 //103 //102 //101 //100 // 99 +#define SAVE_GAME_VERSION 132 //131 //125 //124 //114 //113 //112 //111 //110 //109 //108 //107 //106 //105 //104 //103 //102 //101 //100 // 99 +#define ZOMBIE_CHANGES 132 // Included some changes for Zombies #define JA25_UB_INTEGRATION 131 // Before this, the JA25 Unfinished Business code was not integrated (see "Builddefines.h" JA2UB) #define IMPROVED_INTERRUPT_SYSTEM 130 #define ENCYCLOPEDIA_SAVEGAME_CHANGE 129 // Added encyclopedia diff --git a/Laptop/personnel.cpp b/Laptop/personnel.cpp index bab4bb43..6e29350d 100644 --- a/Laptop/personnel.cpp +++ b/Laptop/personnel.cpp @@ -4959,7 +4959,7 @@ 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]); - 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)); + 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)); 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); @@ -6824,7 +6824,7 @@ DEF:3/19/99: case 14: // kills mprintf((INT16)(pPersonnelScreenPoints[20].x+(iSlot*TEXT_BOX_WIDTH)),(pPersonnelScreenPoints[20].y - 12),pPersonnelScreenStrings[PRSNL_TXT_KILLS]); - 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)); + 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)); 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); @@ -8235,6 +8235,14 @@ void AssignPersonnelKillsHelpText( INT32 IMercId ) swprintf(atStr, pPersonnelRecordsHelpTexts[ 6 ], gMercProfiles[Menptr[IMercId].ubProfile].records.usKillsOthers ); wcscat( apStr, atStr ); } + if (gGameSettings.fOptions[TOPTION_ZOMBIES] ) + { + if (gMercProfiles[Menptr[IMercId].ubProfile].records.usKillsZombies > 0 || fShowRecordsIfZero) + { + swprintf(atStr, pPersonnelRecordsHelpTexts[ 46 ], gMercProfiles[Menptr[IMercId].ubProfile].records.usKillsZombies ); + wcscat( apStr, atStr ); + } + } // Set region help text SetRegionFastHelpText( &(gSkillTraitHelpTextRegion[7]), apStr ); diff --git a/Multiplayer/client.cpp b/Multiplayer/client.cpp index 452d340a..12226c9e 100644 --- a/Multiplayer/client.cpp +++ b/Multiplayer/client.cpp @@ -3570,7 +3570,7 @@ void recieveEXPLOSIONDAMAGE (RPCParameters *rpcParameters) // can use DishOutGasDamage() as it is dependant on the local state of the gas cloud which is not always in sync // but we have the definite results of damage on a merc, so : - pSoldier->SoldierTakeDamage( ANIM_STAND, exp->sWoundAmt, exp->sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_STAND, exp->sWoundAmt, 0, exp->sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE ); } else if (exp->ubDamageFunc == 2) { @@ -4405,7 +4405,7 @@ void recieveDISCONNECT(RPCParameters* rpcParameters) if ( !pTeamSoldier->aiData.bNeutral && (pTeamSoldier->bTeam == iNetbTeam ) ) { // KIll...... - pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); + pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 0, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); } } } diff --git a/Options Screen.cpp b/Options Screen.cpp index 7618cc8be2da51a8420731c8f2d4eeb3905705d2..d50096f9622dccab10d9491e54f89363d96353dd 100644 GIT binary patch delta 180 zcmezIp8d~z_6=7uIQ1Fy88R8t7&N9|oX99Y`A0?^Sg4wTmw{{Y+#2u60V+JxKS(jM zO#d^PQDE|+%o0|RmfFdN=Y^ZSvbK9=F<#J|K4CJW6mJxRKZ7rW6N4v%D?{+~Ks!d^ w>Gzg07ERA{XB60eXA0vg9^%xQuuMO9k&$ism;HwornSnowCamo, sizeof(wornSnowCamo), sizeof(INT8), numBytesRead); numBytesRead = ReadFieldByField(hFile, &this->sFacilityTypeOperated, sizeof(sFacilityTypeOperated), sizeof(INT16), numBytesRead); numBytesRead = ReadFieldByField(hFile, &this->bScopeMode, sizeof(bScopeMode), sizeof(INT8), numBytesRead); + numBytesRead = ReadFieldByField(hFile, &this->bPoisonBleeding, sizeof(bPoisonBleeding), sizeof(INT8), numBytesRead); + numBytesRead = ReadFieldByField(hFile, &this->bPoisonLife, sizeof(bPoisonLife), sizeof(INT8), numBytesRead); + numBytesRead = ReadFieldByField(hFile, &this->bPoisonSum, sizeof(bPoisonSum), sizeof(INT8), numBytesRead); + numBytesRead = ReadFieldByField(hFile, &this->bPoisonResistance, sizeof(bPoisonResistance), sizeof(INT16), numBytesRead); + numBytesRead = ReadFieldByField(hFile, &this->bPoisonAbsorption, sizeof(bPoisonAbsorption), sizeof(INT16), numBytesRead); + #ifdef JA2UB numBytesRead = ReadFieldByField(hFile, &this->fIgnoreGetupFromCollapseCheck, sizeof(fIgnoreGetupFromCollapseCheck), sizeof(BOOLEAN), numBytesRead); numBytesRead = ReadFieldByField(hFile, &this->GetupFromJA25StartCounter, sizeof(GetupFromJA25StartCounter), sizeof(TIMECOUNTER), numBytesRead); @@ -2181,6 +2187,9 @@ BOOLEAN SOLDIERTYPE::Load(HWFILE hFile) buffer = 28; } numBytesRead += buffer; + + INT32 sizestruct = sizeof(STRUCT_Statistics); + if(numBytesRead != sizeof(STRUCT_Statistics)) return(FALSE); // if ( !FileRead( hFile, &this->stats, sizeof(STRUCT_Statistics), &uiNumBytesRead ) ) diff --git a/Strategic/Assignments.cpp b/Strategic/Assignments.cpp index d960ea11..21d31266 100644 --- a/Strategic/Assignments.cpp +++ b/Strategic/Assignments.cpp @@ -571,6 +571,8 @@ static BOOLEAN IsGunJammed(const OBJECTTYPE* pObj); /// Collect items that need repairing and add them to the repair queue static void CollectRepairableItems(const SOLDIERTYPE* pSoldier, RepairQueue& itemsToFix); +extern BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse ); + void InitSectorsWithSoldiersList( void ) { // init list of sectors @@ -1121,6 +1123,11 @@ BOOLEAN CanCharacterPatient( SOLDIERTYPE *pSoldier ) if ( pSoldier->ubCriticalStatDamage[i] > 0 ) return ( TRUE ); } + + // Flugente: check if poisoned + if ( pSoldier->bPoisonSum > 0 ) + return( TRUE ); + // if we don't have damaged stat, look if we need healing if ( pSoldier->stats.bLife == pSoldier->stats.bLifeMax ) return( FALSE ); @@ -2669,8 +2676,8 @@ void UpdatePatientsWhoAreDoneHealing( void ) // active soldier? if( pTeamSoldier->bActive ) { - // patient who doesn't need healing - if( ( pTeamSoldier->bAssignment == PATIENT ) &&( pTeamSoldier->stats.bLife == pTeamSoldier->stats.bLifeMax ) ) + // patient who doesn't need healing or curing + if( ( pTeamSoldier->bAssignment == PATIENT ) &&( pTeamSoldier->stats.bLife == pTeamSoldier->stats.bLifeMax ) && ( pTeamSoldier->bPoisonSum == 0 ) ) { // SANDRO - added check if we can help to heal lost stats to this one for (UINT8 cnt = 0; cnt < NUM_DAMAGABLE_STATS; cnt++) @@ -2937,8 +2944,21 @@ BOOLEAN CanSoldierBeHealedByDoctor( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pDoctor, // if we have no damaged stat and don't need healing if (!fHealDamagedStat && (pSoldier->stats.bLife == pSoldier->stats.bLifeMax) ) { - // cannot be healed - return( FALSE ); + // if we are poisoned + if ( pSoldier->bPoisonSum ) + { + // if using the new trait system, Doctor skill is needed + if ( gGameOptions.fNewTraitSystem && ( NUM_SKILL_TRAITS( pDoctor, DOCTOR_NT ) == 0 ) ) + { + // we may have the medical stats, but we lack the skills! + return( FALSE ); + } + } + else // if we are not poisoned + { + // cannot be healed + return( FALSE ); + } } return( TRUE ); @@ -2954,6 +2974,12 @@ UINT8 GetMinHealingSkillNeeded( SOLDIERTYPE *pPatient ) // less than ok life, return skill needed return( gGameExternalOptions.ubBaseMedicalSkillToDealWithEmergency + ( gGameExternalOptions.ubMultiplierForDifferenceInLifeValueForEmergency * ( OKLIFE - pPatient->stats.bLife ) ) ); } + // if at full life, only poison needs to be removed + else if ( pPatient->stats.bLife == pPatient->stats.bLifeMax && pPatient->bPoisonSum > 0 ) + { + // skill to remove poison needed + return( gGameExternalOptions.ubPoisonBaseMedicalSkillToCure ); + } else { // only need some skill @@ -2967,16 +2993,19 @@ UINT16 HealPatient( SOLDIERTYPE *pPatient, SOLDIERTYPE * pDoctor, UINT16 usHundr ////////////////////////////////////////////////////////////////////////////// // SANDRO - this whole procedure was heavily changed //////////////////////////////////////////////////// - UINT16 usHealingPtsLeft; + UINT16 usHealingPtsLeft = 0; UINT16 usTotalHundredthsUsed = 0; INT16 sPointsToUse = 0; INT8 bPointsHealed = 0; - INT8 bMedFactor; + INT8 bMedFactor = 1; // basic medical factor BOOLEAN fWillRepiarStats = FALSE; - BOOLEAN fWillHealLife = TRUE; + BOOLEAN fWillHealLife = TRUE; UINT16 usTotalMedPoints = 0; UINT16 ubReturnDamagedStatRate = 0; + // usPointsLeftToCurePoison will measure how much Hundreths will be left to cure poison + INT16 usPointsLeftToCurePoison = usHundredthsHealed; + // Look how much life do we need to heal sPointsToUse = ( pPatient->stats.bLifeMax - pPatient->stats.bLife ); if ( sPointsToUse <= 0 ) @@ -3008,7 +3037,6 @@ UINT16 HealPatient( SOLDIERTYPE *pPatient, SOLDIERTYPE * pDoctor, UINT16 usHundr sPointsToUse = ( INT8 )usHealingPtsLeft; } - bMedFactor = 1; // basic medical factor // if we will heal life and stats at the same time, increases the medical cost if (fWillRepiarStats && fWillHealLife) { @@ -3071,6 +3099,10 @@ UINT16 HealPatient( SOLDIERTYPE *pPatient, SOLDIERTYPE * pDoctor, UINT16 usHundr } } + // we have to remember what will be left over to cure poison + //usPointsLeftToCurePoison = min(usHundredthsHealed, usPointsLeftToCurePoison - usTotalHundredthsUsed); + //usPointsLeftToCurePoison = max(usPointsLeftToCurePoison, 0); // do not go below zero + // if we are actually here to heal life if ( fWillHealLife ) { @@ -3084,6 +3116,16 @@ UINT16 HealPatient( SOLDIERTYPE *pPatient, SOLDIERTYPE * pDoctor, UINT16 usHundr pPatient->sFractLife %= 100; pPatient->stats.bLife = min( pPatient->stats.bLifeMax, (pPatient->stats.bLife + bPointsHealed)); + + // potentially increase bPoisonLife: as life points are healed, unhealed poinson points become healed poison points + if ( pPatient->bPoisonSum > 0 ) + { + // check if there are unhealed poison points + if ( pPatient->bPoisonLife < pPatient->bPoisonSum ) + { + pPatient->bPoisonLife = min( pPatient->bPoisonSum, (pPatient->bPoisonLife + bPointsHealed)); + } + } } else if (((pPatient->sFractLife / gGameExternalOptions.ubPointCostPerHealthBelowOkLife) >= 100) && (pPatient->stats.bLife < OKLIFE)) { @@ -3091,6 +3133,16 @@ UINT16 HealPatient( SOLDIERTYPE *pPatient, SOLDIERTYPE * pDoctor, UINT16 usHundr pPatient->sFractLife %= 100; pPatient->stats.bLife = min( pPatient->stats.bLifeMax, (pPatient->stats.bLife + bPointsHealed)); + + // potentially increase bPoisonLife: as life points are healed, unhealed poinson points become healed poison points + if ( pPatient->bPoisonSum > 0 ) + { + // check if there are unhealed poison points + if ( pPatient->bPoisonLife < pPatient->bPoisonSum ) + { + pPatient->bPoisonLife = min( pPatient->bPoisonSum, (pPatient->bPoisonLife + bPointsHealed)); + } + } } // when being healed normally, reduce insta-healable HPs value @@ -3113,6 +3165,65 @@ UINT16 HealPatient( SOLDIERTYPE *pPatient, SOLDIERTYPE * pDoctor, UINT16 usHundr } } + // As curing poison happens only when there is no more life to heal ortats to repair, we don't have to edit the above function - we simply cure afterwards + // Look how much poison needs to be cured. Conversion of fresh life points to poison points has already taken place + if ( pPatient->bPoisonSum > 0 || pPatient->sFractLife < 0 ) + { + if ( fWillHealLife || fWillRepiarStats ) + usPointsLeftToCurePoison = max(0, usPointsLeftToCurePoison - usHundredthsHealed); + + if ( usPointsLeftToCurePoison > 0 ) + { + INT8 usPoisontoCure = pPatient->bPoisonSum; + + if ( (INT8) (usPointsLeftToCurePoison/100) < usPoisontoCure ) + usPoisontoCure = (INT8) (usPointsLeftToCurePoison/100); + + // calculate how much total points we have in all medical bags + UINT16 usTotalMedPointsLeft = TotalMedicalKitPoints(pDoctor); + + if ( usTotalMedPointsLeft > 0 ) + { + UINT16 usTotalHundredthsUsedToCurePoison = 0; + // if having enough, no problem + if (usTotalMedPointsLeft >= (usPoisontoCure * bMedFactor)) + { + usTotalHundredthsUsedToCurePoison = usPoisontoCure * 100; + usTotalMedPointsLeft = (usPoisontoCure * bMedFactor); + } + else + { + // only heal what we have + usTotalHundredthsUsedToCurePoison = (usTotalMedPointsLeft * 100 / bMedFactor) ; + } + + pPatient->sFractLife += usTotalHundredthsUsedToCurePoison; + + // modify usHundredthsHealed? + //usHundredthsHealed += usTotalHundredthsUsedToCurePoison; + + if (pPatient->sFractLife >= 100) + { + // convert fractions into full points + INT8 bPoisonPointsHealed = (pPatient->sFractLife / 100); + pPatient->sFractLife %= 100; + + pPatient->bPoisonSum = max( 0, (pPatient->bPoisonSum - bPoisonPointsHealed)); + pPatient->bPoisonLife = max( 0, (pPatient->bPoisonLife - bPoisonPointsHealed)); + } + + // Finally use all kit points (we are sure, we have that much) + if (UseTotalMedicalKitPoints( pDoctor, usTotalMedPointsLeft ) == FALSE ) + { + // throw message if this went wrong for feedback on debugging +#ifdef JA2TESTVERSION + ScreenMsg( FONT_MCOLOR_RED, MSG_TESTVERSION, L"Warning! UseTotalKitPOints returned false, not all points were probably used." ); +#endif + } + } + } + } + return ( usHundredthsHealed ); } @@ -3176,13 +3287,23 @@ void HealHospitalPatient( SOLDIERTYPE *pPatient, UINT16 usHealingPtsLeft ) // if he needs more than we have, reduce to that if( bPointsToUse > usHealingPtsLeft ) { - bPointsToUse = ( INT8 )usHealingPtsLeft; + bPointsToUse = ( INT8 )usHealingPtsLeft; } usHealingPtsLeft -= bPointsToUse; // heal person the amount / POINT_COST_PER_HEALTH_BELOW_OKLIFE pPatient->stats.bLife += ( bPointsToUse / gGameExternalOptions.ubPointCostPerHealthBelowOkLife ); + + // potentially increase bPoisonLife: as life points are healed, unhealed poinson points become healed poison points + if ( pPatient->bPoisonSum > 0 ) + { + // check if there are unhealed poison points + if ( pPatient->bPoisonLife < pPatient->bPoisonSum ) + { + pPatient->bPoisonLife = min( pPatient->bPoisonSum, (pPatient->bPoisonLife + ( bPointsToUse / gGameExternalOptions.ubPointCostPerHealthBelowOkLife ) ) ); + } + } // SANDRO - doctor trait - when being healed normally, reduce insta-healable HPs value if ( gGameOptions.fNewTraitSystem && pPatient->iHealableInjury > 0 ) @@ -3209,6 +3330,16 @@ void HealHospitalPatient( SOLDIERTYPE *pPatient, UINT16 usHealingPtsLeft ) // heal person the amount pPatient->stats.bLife += bPointsToUse; + + // potentially increase bPoisonLife: as life points are healed, unhealed poinson points become healed poison points + if ( pPatient->bPoisonSum > 0 ) + { + // check if there are unhealed poison points + if ( pPatient->bPoisonLife < pPatient->bPoisonSum ) + { + pPatient->bPoisonLife = min( pPatient->bPoisonSum, (pPatient->bPoisonLife + bPointsToUse) ); + } + } // SANDRO - doctor trait - when being healed normally, reduce insta-healable HPs value if ( gGameOptions.fNewTraitSystem && pPatient->iHealableInjury > 0 ) @@ -3219,8 +3350,21 @@ void HealHospitalPatient( SOLDIERTYPE *pPatient, UINT16 usHealingPtsLeft ) } } - // if this patient is fully healed - if ( pPatient->stats.bLife == pPatient->stats.bLifeMax ) + // are here still points left to cure poison? + if ( usHealingPtsLeft > 0 ) + { + UINT16 usPoisonToCure = pPatient->bPoisonSum; + + if ( usPoisonToCure > usHealingPtsLeft ) + usPoisonToCure = usHealingPtsLeft; + + usHealingPtsLeft -= usPoisonToCure; + + pPatient->bPoisonSum = max( 0, (pPatient->bPoisonSum - usPoisonToCure)); + } + + // if this patient is fully healed and cured + if ( pPatient->stats.bLife == pPatient->stats.bLifeMax && pPatient->bPoisonSum == 0 ) { AssignmentDone( pPatient, TRUE, TRUE ); } @@ -4550,7 +4694,7 @@ INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE switch( bTrainStat ) { case( STRENGTH ): - bTrainerEffSkill = EffectiveStrength ( pInstructor ); + bTrainerEffSkill = (INT8)EffectiveStrength ( pInstructor ); bTrainerNatSkill = pInstructor->stats.bStrength; break; case( DEXTERITY ): @@ -5428,6 +5572,7 @@ void HandleNaturalHealing( void ) void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) { UINT32 uiPercentHealth = 0; + UINT32 uiPercentPoison = 0; INT8 bActivityLevelDivisor = 0; UINT16 usFacilityModifier = 100; UINT8 ubAssignmentType = 0; @@ -5446,7 +5591,7 @@ void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) } // lost any pts? - if( pSoldier->stats.bLife == pSoldier->stats.bLifeMax ) + if( pSoldier->stats.bLife == pSoldier->stats.bLifeMax && pSoldier->bPoisonLife == 0 ) { return; } @@ -5498,6 +5643,9 @@ void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) // what percentage of health is he down to uiPercentHealth = ( pSoldier->stats.bLife * 100 ) / pSoldier->stats.bLifeMax; + // how much is he poisoned? + uiPercentPoison = ( pSoldier->bPoisonSum * 100 ) / pSoldier->stats.bLifeMax; + // SANDRO - experimental - increase health regenariton of soldiers when doctors are around if ( gGameOptions.fNewTraitSystem ) { @@ -5539,6 +5687,11 @@ void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) pSoldier->sFractLife += ( INT16 ) ((( uiPercentHealth / bActivityLevelDivisor ) * usFacilityModifier) / 100 ); } + // Flugente: reduce gained life because of poison + // poison resistance reduces poison damage + // a high activity level (stress) increases poisoning + pSoldier->sFractLife -= ( INT16 ) ( (uiPercentPoison * (100 - pSoldier->GetPoisonResistance())/100) * gGameExternalOptions.sPoisonInfectionDamageMultiplier * ((99 + bActivityLevelDivisor)/100) ); + // now update the real life values UpDateSoldierLife( pSoldier ); @@ -5549,12 +5702,45 @@ void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) void UpDateSoldierLife( SOLDIERTYPE *pSoldier ) { // update soldier life, make sure we don't go out of bounds - pSoldier->stats.bLife += pSoldier->sFractLife / 100; + INT8 sAddedLife = pSoldier->sFractLife/100; + + // if sAddedLife, it must be poison damage + if ( sAddedLife < 0 ) + { + // it is hereby possible to heal yourself through poison, if you have high absorption ( > 100%) + INT8 sPoisonAbsorped = - (INT8) ( sAddedLife * (pSoldier->GetPoisonAbsorption()/100) ); + + sAddedLife += sPoisonAbsorped; + + // if we're not fully poisoned, we are now poisoned a bit more, even if we won life thanks to absorption + if ( pSoldier->bPoisonSum < pSoldier->stats.bLifeMax ) + { + pSoldier->bPoisonSum += abs(sAddedLife); + } + } + + INT8 oldlife = pSoldier->stats.bLife; + pSoldier->stats.bLife += sAddedLife; + + // if we fall below OKLIFE (can only be because of poison), we start bleeding again... + if ( pSoldier->stats.bLife < OKLIFE && oldlife >= OKLIFE && sAddedLife < 0 ) + { + /*pSoldier->stats.bLife = 0; + BOOLEAN fMadeCorpse; + HandleSoldierDeath( pSoldier, &fMadeCorpse );*/ + + pSoldier->bBleeding = pSoldier->stats.bLifeMax - pSoldier->stats.bLife; + pSoldier->bPoisonBleeding = pSoldier->bPoisonSum - pSoldier->bPoisonLife; + + /*pSoldier->bPoisonSum = 0; + pSoldier->bPoisonLife = 0; + pSoldier->bPoisonBleeding = 0;*/ + } // SANDRO - when being healed normally, reduce insta-healable HPs value if ( gGameOptions.fNewTraitSystem && pSoldier->iHealableInjury > 0 ) { - pSoldier->iHealableInjury -= pSoldier->sFractLife; + pSoldier->iHealableInjury -= sAddedLife * 100; if (pSoldier->iHealableInjury < 0) pSoldier->iHealableInjury = 0; @@ -5568,7 +5754,11 @@ void UpDateSoldierLife( SOLDIERTYPE *pSoldier ) { // reduce pSoldier->stats.bLife = pSoldier->stats.bLifeMax; - pSoldier->sFractLife = 0; + + // only set sFractLife to be 0 if > 0, saving possible poison damage + if ( pSoldier->sFractLife > 0 ) + pSoldier->sFractLife = 0; + pSoldier->iHealableInjury = 0; // check added by SANDRO } return; @@ -8107,7 +8297,9 @@ void BeginRemoveMercFromContract( SOLDIERTYPE *pSoldier ) // WANNE: Nothing to do here, when we want to dismiss the robot BOOLEAN fAmIaRobot = AM_A_ROBOT( pSoldier ); - if (!fAmIaRobot) + + // Flugente: If merc is unconscious, just fire him anyway (if talking stuff is called, this leads to a geme lock) + if (!fAmIaRobot && pSoldier->stats.bLife > CONSCIOUSNESS ) { if( ( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__MERC ) || ( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__NPC ) ) { @@ -10938,6 +11130,7 @@ void SetSoldierAssignment( SOLDIERTYPE *pSoldier, INT8 bAssignment, INT32 iParam pSoldier->bOldAssignment = pSoldier->bAssignment; pSoldier->bBleeding = 0; + pSoldier->bPoisonBleeding = 0; // set dirty flag fTeamPanelDirty = TRUE; @@ -12219,6 +12412,7 @@ void BandageBleedingDyingPatientsBeingTreated( ) // stop bleeding automatically pSoldier->bBleeding = 0; + pSoldier->bPoisonBleeding = 0; if ( pSoldier->stats.bLife < OKLIFE ) { @@ -12227,7 +12421,14 @@ void BandageBleedingDyingPatientsBeingTreated( ) { pSoldier->iHealableInjury -= ((OKLIFE - pSoldier->stats.bLife) * 100); } + + // convert poison points to poison life points + INT8 oldlife = pSoldier->stats.bLife; + pSoldier->stats.bLife = OKLIFE; + + INT8 lifegained = OKLIFE - oldlife; + pSoldier->bPoisonLife = min(pSoldier->bPoisonSum, pSoldier->bPoisonLife + lifegained); } } else // assigned to DOCTOR/PATIENT @@ -16417,3 +16618,4 @@ void HaveMercSayWhyHeWontLeave( SOLDIERTYPE *pSoldier ) } } #endif + diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index 8dbc0c6b..bb10e437 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -729,6 +729,9 @@ void RefreshMerc( SOLDIERTYPE *pSoldier ) { pSoldier->stats.bLife = pSoldier->stats.bLifeMax; pSoldier->bBleeding = 0; + pSoldier->bPoisonSum = 0; + pSoldier->bPoisonLife = 0; + pSoldier->bPoisonBleeding = 0; pSoldier->iHealableInjury = 0; // added by SANDRO pSoldier->bBreath = pSoldier->bBreathMax = 100; pSoldier->sBreathRed = 0; @@ -1189,14 +1192,43 @@ void RenderSoldierCellBars( SOLDIERCELL *pCell ) iStartY = pCell->yp + 29 - 25*pCell->pSoldier->stats.bLifeMax/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) ); + + // poisoned bleeding in purple + if ( pCell->pSoldier->bPoisonBleeding ) + { + iStartY = pCell->yp + 29 - 25*(pCell->pSoldier->stats.bLifeMax - pCell->pSoldier->bBleeding + pCell->pSoldier->bPoisonBleeding)/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 57, 107 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 115, 181 ) ) ); + } + //pink one for bandaged. - iStartY += 25*pCell->pSoldier->bBleeding/100; + iStartY = pCell->yp + 29 - 25*(pCell->pSoldier->stats.bLifeMax - pCell->pSoldier->bBleeding)/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) ); + + // get amount of poisoned bandage + INT8 bPoisonBandage = pCell->pSoldier->bPoisonSum - pCell->pSoldier->bPoisonBleeding - pCell->pSoldier->bPoisonLife; + if ( bPoisonBandage ) + { + // poisoned bandage in bright green + iStartY = pCell->yp + 29 - 25*(pCell->pSoldier->stats.bLife + bPoisonBandage)/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 57, 156, 57 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 132, 222, 132 ) ) ); + } + //red one for actual health iStartY = pCell->yp + 29 - 25*pCell->pSoldier->stats.bLife/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) ); + + // poisoned life + if ( pCell->pSoldier->bPoisonLife ) + { + iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bPoisonLife/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+37, iStartY, pCell->xp+38, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+38, iStartY, pCell->xp+39, pCell->yp+29, Get16BPPColor( FROMRGB( 0, 206, 0 ) ) ); + } + //BREATH BAR iStartY = pCell->yp + 29 - 25*pCell->pSoldier->bBreathMax/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, pCell->xp+41, iStartY, pCell->xp+42, pCell->yp+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) ); @@ -1519,6 +1551,11 @@ UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OB pVictim->stats.bLife = OKLIFE; // reduce bleeding by the same number of life points healed up pVictim->bBleeding -= bBelowOKlife; + + // Flugente: increase bPoisonLife, decrease bPoisonBleeding + pVictim->bPoisonLife = min(pVictim->bPoisonSum, pVictim->bPoisonLife + bBelowOKlife); + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - bBelowOKlife); + // use up appropriate # of actual healing points bPtsLeft -= (2 * bBelowOKlife); } @@ -1533,12 +1570,20 @@ UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OB } pVictim->stats.bLife += ( bPtsLeft / 2); pVictim->bBleeding -= ( bPtsLeft / 2); + + // Flugente: increase bPoisonLife, decrease bPoisonBleeding + pVictim->bPoisonLife = min(pVictim->bPoisonSum, pVictim->bPoisonLife + ( bPtsLeft / 2)); + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - ( bPtsLeft / 2)); + bPtsLeft = bPtsLeft % 2; // if ptsLeft was odd, ptsLeft = 1 } // this should never happen any more, but make sure bleeding not negative if (pVictim->bBleeding < 0) + { pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; + } // if this healing brought the patient out of the worst of it, cancel dying if (pVictim->stats.bLife >= OKLIFE ) @@ -1612,22 +1657,27 @@ UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OB if ((pVictim->stats.bLife + (iLifeReturned / 100)) <= pVictim->stats.bLifeMax) { pVictim->stats.bLife += (iLifeReturned/100); + pVictim->bPoisonLife = min(pVictim->bPoisonSum, pVictim->bPoisonLife + (iLifeReturned/100)); if (pVictim->bBleeding >= (iLifeReturned/100)) { pVictim->bBleeding -= (iLifeReturned/100); + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - (iLifeReturned/100)); uiMedcost += (iLifeReturned / 200); // add medkit points cost for unbandaged part } else { pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; uiMedcost += max( 0, (((iLifeReturned/100) - pVictim->bBleeding) / 2)); // add medkit points cost for unbandaged part } } else // this shouldn't even happen, but we still want to have it here for sure { pVictim->stats.bLife = pVictim->stats.bLifeMax; + pVictim->bPoisonLife = pVictim->bPoisonSum; pVictim->iHealableInjury = 0; pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; } // Reduce max breath based on life returned if ( (pVictim->bBreathMax - (((iLifeReturned/100) * gSkillTraitValues.usDOSurgeryMaxBreathLoss )/ 100)) <= BREATHMAX_ABSOLUTE_MINIMUM ) @@ -1654,10 +1704,12 @@ UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OB { bPtsLeft -= pVictim->bBleeding; pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; } else // bandage what we can { pVictim->bBleeding -= bPtsLeft; + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - bPtsLeft); bPtsLeft = 0; } } @@ -4541,13 +4593,16 @@ void AttackTarget( SOLDIERCELL *pAttacker, SOLDIERCELL *pTarget ) case SOLDIER_CLASS_CREATURE : gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsCreatures++; break; + case SOLDIER_CLASS_ZOMBIE : + gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsZombies++; + break; default : if ( CREATURE_OR_BLOODCAT( pTarget->pSoldier ) ) gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsCreatures++; - else if ( TANK( pTarget->pSoldier ) ) // we hardly can kill a tank in autoresolve, but well.. who knows.. - gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsTanks++; - else if ( pTarget->pSoldier->bTeam == CIV_TEAM && !pTarget->pSoldier->aiData.bNeutral && pTarget->pSoldier->bSide != gbPlayerNum ) - gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsHostiles++; + else if ( TANK( pTarget->pSoldier ) ) // we hardly can kill a tank in autoresolve, but well.. who knows.. + gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsTanks++; + else if ( pTarget->pSoldier->bTeam == CIV_TEAM && !pTarget->pSoldier->aiData.bNeutral && pTarget->pSoldier->bSide != gbPlayerNum ) + gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsHostiles++; else gMercProfiles[ pAttacker->pSoldier->ubProfile ].records.usKillsOthers++; break; @@ -4729,6 +4784,9 @@ void TargetHitCallback( SOLDIERCELL *pTarget, INT32 index ) case SOLDIER_CLASS_CREATURE : gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsCreatures++; break; + case SOLDIER_CLASS_ZOMBIE : + gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsZombies++; + break; default : if ( CREATURE_OR_BLOODCAT( pTarget->pSoldier ) ) gMercProfiles[ pKiller->pSoldier->ubProfile ].records.usKillsCreatures++; diff --git a/Strategic/Facilities.cpp b/Strategic/Facilities.cpp index 460e75fc..7e63b6a8 100644 --- a/Strategic/Facilities.cpp +++ b/Strategic/Facilities.cpp @@ -1722,7 +1722,7 @@ void HandleRisksForSoldierFacilityAssignment( SOLDIERTYPE *pSoldier, UINT8 ubFac if (Result < 0) { // Soldier is suffering direct unbandaged injury. - pSoldier->SoldierTakeDamage( 0, abs(Result), abs(Result), TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, FALSE ); + pSoldier->SoldierTakeDamage( 0, abs(Result), 0, abs(Result), TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, FALSE ); fBadResult = TRUE; // SANDRO - add to merc records - facility accidents counter diff --git a/Strategic/Map Screen Interface.cpp b/Strategic/Map Screen Interface.cpp index 8cf41f75..ecbc582e 100644 --- a/Strategic/Map Screen Interface.cpp +++ b/Strategic/Map Screen Interface.cpp @@ -1972,10 +1972,23 @@ void UpdateCharRegionHelpText( void ) { // person (health/energy/morale) GetMoraleString( pSoldier, pMoraleStr ); - swprintf( sString, L"%s: %d/%d, %s: %d/%d, %s: %s", - pMapScreenStatusStrings[ 0 ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, - pMapScreenStatusStrings[ 1 ], pSoldier->bBreath, pSoldier->bBreathMax, - pMapScreenStatusStrings[ 2 ], pMoraleStr ); + + if ( pSoldier->bPoisonSum ) + { + INT8 bPoisonBandaged = pSoldier->bPoisonSum - pSoldier->bPoisonBleeding - pSoldier->bPoisonLife; + swprintf( sString, L"%s: %d/%d, %s: %d/%d/%d - %d, %s: %d/%d, %s: %s", + pMapScreenStatusStrings[ 0 ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, + pMapScreenStatusStrings[ 5 ], pSoldier->bPoisonBleeding, bPoisonBandaged, pSoldier->bPoisonLife, pSoldier->bPoisonSum, + pMapScreenStatusStrings[ 1 ], pSoldier->bBreath, pSoldier->bBreathMax, + pMapScreenStatusStrings[ 2 ], pMoraleStr ); + } + else + { + swprintf( sString, L"%s: %d/%d, %s: %d/%d, %s: %s", + pMapScreenStatusStrings[ 0 ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, + pMapScreenStatusStrings[ 1 ], pSoldier->bBreath, pSoldier->bBreathMax, + pMapScreenStatusStrings[ 2 ], pMoraleStr ); + } } } else @@ -5358,16 +5371,42 @@ void RenderSoldierSmallFaceForUpdatePanel( INT32 iIndex, INT32 iX, INT32 iY ) ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) ); ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) ); + if ( pSoldier->bPoisonBleeding ) + { + // poisoned bleeding in purple + iStartY = iY + 29 - 27*(pSoldier->stats.bLifeMax - pSoldier->bBleeding + pSoldier->bPoisonBleeding)/100; + ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 107, 57, 107 ) ) ); + ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 222, 115, 181 ) ) ); + } + //pink one for bandaged. - iStartY += 27*pSoldier->bBleeding/100; + iStartY = iY + 29 - 27*(pSoldier->stats.bLifeMax - pSoldier->bBleeding)/100; ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) ); ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) ); + // get amount of poisoned bandage + INT8 bPoisonBandage = pSoldier->bPoisonSum - pSoldier->bPoisonBleeding - pSoldier->bPoisonLife; + if ( bPoisonBandage ) + { + // poisoned bandage in bright green + iStartY = iY + 29 - 27*(pSoldier->stats.bLife + bPoisonBandage)/100; + ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 57, 156, 57 ) ) ); + ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 132, 222, 132 ) ) ); + } + //red one for actual health iStartY = iY + 29 - 27*pSoldier->stats.bLife/100; ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) ); ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) ); + // poisoned life + if ( pSoldier->bPoisonLife ) + { + iStartY = iY + 29 - 27*pSoldier->bPoisonLife/100; + ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+36, iStartY, iX+37, iY+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) ); + ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+37, iStartY, iX+38, iY+29, Get16BPPColor( FROMRGB( 0, 206, 0 ) ) ); + } + //BREATH BAR iStartY = iY + 29 - 27*pSoldier->bBreathMax/100; ColorFillVideoSurfaceArea( guiSAVEBUFFER, iX+39, iStartY, iX+40, iY+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) ); diff --git a/Strategic/Merc Contract.cpp b/Strategic/Merc Contract.cpp index bae07304..9bd697d2 100644 --- a/Strategic/Merc Contract.cpp +++ b/Strategic/Merc Contract.cpp @@ -496,6 +496,10 @@ BOOLEAN WillMercRenew( SOLDIERTYPE *pSoldier, BOOLEAN fSayQuote ) if( pSoldier->ubWhatKindOfMercAmI != MERC_TYPE__AIM_MERC ) return( FALSE ); + // Flugente: an unconscious merc can't say 'yes' to a contract renewal + if ( pSoldier->stats.bLife < CONSCIOUSNESS ) + return( FALSE ); + // does the merc have another contract already lined up? if( pSoldier->flags.fSignedAnotherContract ) { diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index 49482c76..f59441ae 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -2174,6 +2174,7 @@ void EnemyCapturesPlayerSoldier( SOLDIERTYPE *pSoldier ) //Bandaging him would prevent him from dying (due to low HP) pSoldier->bBleeding = 0; + pSoldier->bPoisonBleeding = 0; // wake him up if ( pSoldier->flags.fMercAsleep ) @@ -2183,6 +2184,7 @@ void EnemyCapturesPlayerSoldier( SOLDIERTYPE *pSoldier ) } //Set his life to 50% + or - 10 HP. + INT8 oldlife = pSoldier->stats.bLife; pSoldier->stats.bLife = pSoldier->stats.bLifeMax / 2; if ( pSoldier->stats.bLife <= 35 ) { @@ -2192,6 +2194,14 @@ void EnemyCapturesPlayerSoldier( SOLDIERTYPE *pSoldier ) { pSoldier->stats.bLife += (INT8)(10 - Random( 21 ) ); } + + // adjust poison points + INT8 lifechanged = oldlife - pSoldier->stats.bLife; + if ( lifechanged < 0 ) + pSoldier->bPoisonLife -= lifechanged; + else if ( lifechanged > 0) + pSoldier->bPoisonLife = max(pSoldier->bPoisonSum, pSoldier->bPoisonLife + lifechanged); + // SANDRO - make the lost life insta-healable pSoldier->iHealableInjury = ((pSoldier->stats.bLifeMax - pSoldier->stats.bLife) * 100); diff --git a/Tactical/Auto Bandage.cpp b/Tactical/Auto Bandage.cpp index 8afe3bb7..63528479 100644 --- a/Tactical/Auto Bandage.cpp +++ b/Tactical/Auto Bandage.cpp @@ -1305,16 +1305,42 @@ BOOLEAN RenderSoldierSmallFaceForAutoBandagePanel( INT32 iIndex, INT16 sCurrentX ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+36, iStartY, sCurrentXPosition+37, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+37, iStartY, sCurrentXPosition+38, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) ); + // poisoned bleeding in purple + if ( pSoldier->bPoisonBleeding ) + { + iStartY = sCurrentYPosition + 29 - 27*(pSoldier->stats.bLifeMax - pSoldier->bBleeding + pSoldier->bPoisonBleeding)/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+36, iStartY, sCurrentXPosition+37, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 107, 57, 107 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+37, iStartY, sCurrentXPosition+38, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 222, 115, 181 ) ) ); + } + //pink one for bandaged. - iStartY += 27*pSoldier->bBleeding/100; + iStartY = sCurrentYPosition + 29 - 27*(pSoldier->stats.bLifeMax - pSoldier->bBleeding)/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+36, iStartY, sCurrentXPosition+37, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+37, iStartY, sCurrentXPosition+38, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) ); + // get amount of poisoned bandage + INT8 bPoisonBandage = pSoldier->bPoisonSum - pSoldier->bPoisonBleeding - pSoldier->bPoisonLife; + if ( bPoisonBandage ) + { + // poisoned bandage in bright green + iStartY = sCurrentYPosition + 29 - 27*(pSoldier->stats.bLife + bPoisonBandage)/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+36, iStartY, sCurrentXPosition+37, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 57, 156, 57 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+37, iStartY, sCurrentXPosition+38, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 132, 222, 132 ) ) ); + } + //red one for actual health iStartY = sCurrentYPosition + 29 - 27*pSoldier->stats.bLife/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+36, iStartY, sCurrentXPosition+37, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+37, iStartY, sCurrentXPosition+38, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) ); + // poisoned life + if ( pSoldier->bPoisonLife ) + { + iStartY = sCurrentYPosition + 29 - 27*pSoldier->bPoisonLife/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+36, iStartY, sCurrentXPosition+37, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+37, iStartY, sCurrentXPosition+38, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 0, 206, 0 ) ) ); + } + //BREATH BAR iStartY = sCurrentYPosition + 29 - 27*pSoldier->bBreathMax/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, sCurrentXPosition+39, iStartY, sCurrentXPosition+40, sCurrentYPosition+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) ); diff --git a/Tactical/Civ Quotes.cpp b/Tactical/Civ Quotes.cpp index 20c29d0b..fc1306f3 100644 --- a/Tactical/Civ Quotes.cpp +++ b/Tactical/Civ Quotes.cpp @@ -1035,6 +1035,10 @@ void StartEnemyTaunt( SOLDIERTYPE *pCiv, INT8 iTauntType ) VIDEO_OVERLAY_DESC VideoOverlayDesc; CHAR16 gzTauntQuote[ 320 ]; + // Flugente: zombies don't talk + if ( pCiv->IsZombie() ) + return; + // if we have a different quote on, return, this one is not important if ( gCivQuoteData.bActive ) { diff --git a/Tactical/Drugs And Alcohol.cpp b/Tactical/Drugs And Alcohol.cpp index 607e777e..46e582c0 100644 --- a/Tactical/Drugs And Alcohol.cpp +++ b/Tactical/Drugs And Alcohol.cpp @@ -334,8 +334,9 @@ void HandleEndTurnDrugAdjustments( SOLDIERTYPE *pSoldier ) // bBandaged = BANDAGED( pSoldier ); // increase life + pSoldier->bPoisonLife = max(pSoldier->bPoisonSum, pSoldier->bPoisonLife + pSoldier->stats.bLife - __min( pSoldier->stats.bLife + LIFE_GAIN_PER_REGEN_POINT, pSoldier->stats.bLifeMax ) ); pSoldier->stats.bLife = __min( pSoldier->stats.bLife + LIFE_GAIN_PER_REGEN_POINT, pSoldier->stats.bLifeMax ); - + //SANDRO - Insta-healable injury reduction if( pSoldier->iHealableInjury > 0 ) { @@ -345,12 +346,14 @@ void HandleEndTurnDrugAdjustments( SOLDIERTYPE *pSoldier ) if ( pSoldier->stats.bLife == pSoldier->stats.bLifeMax ) { pSoldier->bBleeding = 0; + pSoldier->bPoisonBleeding = 0; pSoldier->iHealableInjury = 0; } else if ( pSoldier->bBleeding + pSoldier->stats.bLife > pSoldier->stats.bLifeMax ) { // got to reduce amount of bleeding pSoldier->bBleeding = (pSoldier->stats.bLifeMax - pSoldier->stats.bLife); + pSoldier->bPoisonBleeding = min(pSoldier->bPoisonBleeding, (pSoldier->stats.bLifeMax - pSoldier->stats.bLife)); } // decrement counter diff --git a/Tactical/Handle Items.cpp b/Tactical/Handle Items.cpp index b94ab55b..3f8d2bc3 100644 --- a/Tactical/Handle Items.cpp +++ b/Tactical/Handle Items.cpp @@ -4575,7 +4575,7 @@ void SetOffBoobyTrapInMapScreen( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObject ) } // injure the inventory character - pSoldier->SoldierTakeDamage( 0, ubPtsDmg, ubPtsDmg, TAKE_DAMAGE_EXPLOSION, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( 0, ubPtsDmg, 0, ubPtsDmg, TAKE_DAMAGE_EXPLOSION, NOBODY, NOWHERE, 0, TRUE ); // play the sound PlayJA2Sample( EXPLOSION_1, RATE_11025, BTNVOLUME, 1, MIDDLEPAN ); diff --git a/Tactical/Interface Control.cpp b/Tactical/Interface Control.cpp index d8c1031b..53932d90 100644 --- a/Tactical/Interface Control.cpp +++ b/Tactical/Interface Control.cpp @@ -760,13 +760,23 @@ void RenderTopmostTacticalInterface( ) SetFontForeground( FONT_MCOLOR_WHITE ); bool showDamage = true; - if (gGameExternalOptions.ubEnemyHitCount > 0 && pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == CREATURE_TEAM) + if (gGameExternalOptions.ubEnemyHitCount > 0 && ( pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == CREATURE_TEAM) ) showDamage = false; if (showDamage) - { - gprintfdirty( sDamageX, sDamageY, L"-%d", pSoldier->sDamage ); - mprintf( sDamageX, sDamageY, L"-%d", pSoldier->sDamage ); + { + if ( pSoldier->sDamage >= 0 ) + { + gprintfdirty( sDamageX, sDamageY, L"-%d", pSoldier->sDamage ); + mprintf( sDamageX, sDamageY, L"-%d", pSoldier->sDamage ); + } + else // Flugente: it is possible that someone might regain negative damage as zombies can regenerate health through bleeding + { + SetFontForeground( FONT_MCOLOR_LTGREEN ); + gprintfdirty( sDamageX, sDamageY, L"+%d", -pSoldier->sDamage ); + mprintf( sDamageX, sDamageY, L"+%d", -pSoldier->sDamage ); + SetFontForeground( FONT_MCOLOR_WHITE ); + } } else { diff --git a/Tactical/Interface Dialogue.cpp b/Tactical/Interface Dialogue.cpp index 089606af..740d2341 100644 --- a/Tactical/Interface Dialogue.cpp +++ b/Tactical/Interface Dialogue.cpp @@ -2268,6 +2268,7 @@ void HandleNPCDoAction( UINT8 ubTargetNPC, UINT16 usActionCode, UINT8 ubQuoteNum DeleteTalkingMenu(); SetSoldierNeutral( pSoldier ); pSoldier->bBleeding = 0; // make sure he doesn't bleed now... + pSoldier->bPoisonBleeding = 0; RecalculateOppCntsDueToBecomingNeutral( pSoldier ); if ( gTacticalStatus.uiFlags & INCOMBAT ) { @@ -4445,6 +4446,9 @@ UINT32 CalcPatientMedicalCost( SOLDIERTYPE * pSoldier ) uiCost += ( 25 * ( OKLIFE - pSoldier->stats.bLife ) ); } + // charge additional $13 for every poison point that has to be cured + uiCost += 13 * pSoldier->bPoisonSum; + // also charge $2 for each point of bleeding that must be stopped if ( pSoldier->bBleeding > 0 ) { diff --git a/Tactical/Interface Enhanced.cpp b/Tactical/Interface Enhanced.cpp index 502b9673..bbcac6e8 100644 --- a/Tactical/Interface Enhanced.cpp +++ b/Tactical/Interface Enhanced.cpp @@ -95,6 +95,8 @@ UINT32 guiItemInfoArmorIcon; UINT32 guiItemInfoSecondaryIcon; // HEADROCK HAM 4: This replaces "Misc" icons. UINT32 guiItemInfoAdvancedIcon; +// Flugente: added icons for WH40K +UINT32 guiItemInfoWH40KIcon; // HEADROCK HAM 4: New tooltip regions for UDB replace the old regions. #define NUM_UDB_FASTHELP_REGIONS 102 @@ -1376,6 +1378,10 @@ BOOLEAN InternalInitEnhancedDescBox() strcpy( VObjectDesc.ImageFile, "INTERFACE\\ItemInfoAdvancedIcons.STI" ); CHECKF( AddVideoObject( &VObjectDesc, &guiItemInfoAdvancedIcon ) ); + // Flugente: added icons for WH40K + VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE; + strcpy( VObjectDesc.ImageFile, "INTERFACE\\ItemInfoWH40KIcons.STI" ); + CHECKF( AddVideoObject( &VObjectDesc, &guiItemInfoWH40KIcon ) ); } return (TRUE); @@ -1879,7 +1885,7 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr CHAR16 pStr[1000]; ///////////////// PRIMARY DATA - ICONS - for (cnt = 0; cnt < 4; cnt++) + for (cnt = 0; cnt < 5; cnt++) { MSYS_DefineRegion( &gUDBFasthelpRegions[ iRegionsCreated ], (INT16)(gItemDescGenRegions[cnt][0].sLeft), @@ -1919,6 +1925,14 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } } + + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies + { + //////////////////// POISON PERCENTAGE + { + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); + } + } } } @@ -3850,6 +3864,51 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } } } + + // Flugente Zombies + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) + { + if(!cnt) cnt += 2; + + if (cnt >= sFirstLine && cnt < sLastLine) + { + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ]), gzUDBGenIndexTooltipText[ 0 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ] ); + + iRegionsCreated++; + + for (cnt2 = 1; cnt2 < 4; cnt2++) + { + MSYS_DefineRegion( &gUDBFasthelpRegions[ iRegionsCreated ], + (INT16)(gItemDescAdvIndexRegions[cnt-sFirstLine][cnt2].sLeft), + (INT16)(gItemDescAdvIndexRegions[cnt-sFirstLine][cnt2].sTop), + (INT16)(gItemDescAdvIndexRegions[cnt-sFirstLine][cnt2].sRight), + (INT16)(gItemDescAdvIndexRegions[cnt-sFirstLine][cnt2].sBottom), + MSYS_PRIORITY_HIGHEST, MSYS_NO_CURSOR, MSYS_NO_CALLBACK, ItemDescCallback ); + + MSYS_AddRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) + cnt2]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) + cnt2]), gzUDBGenIndexTooltipText[ cnt2 ] ); + SetRegionHelpEndCallback( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) + cnt2]), HelpTextDoneCallback ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) + cnt2] ); + + iRegionsCreated++; + } + } + ++cnt; + + ///////////////////// poison percentage + // only draw if item is poisoned in any way + if ( Item[gpItemDescObject->usItem].bPoisonPercentage != 0 || (Item[gpItemDescObject->usItem].usItemClass == IC_GUN && AmmoTypes[Magazine[ Item[ gpItemDescObject->usItem ].ubClassIndex].ubAmmoType].poisonPercentage != 0 ) ) + { + if (cnt >= sFirstLine && cnt < sLastLine) + { + swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 56 ], szUDBAdvStatsExplanationsTooltipText[ 56 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ] ); + } + cnt++; + } + } gubDescBoxTotalAdvLines = (UINT8)cnt; } @@ -3866,6 +3925,7 @@ void DeleteEnhancedDescBox( UINT32 guiCurrentItemDescriptionScreen ) // HEADROCK HAM 4: Delete new icons DeleteVideoObjectFromIndex( guiItemInfoSecondaryIcon ); DeleteVideoObjectFromIndex( guiItemInfoAdvancedIcon ); + DeleteVideoObjectFromIndex( guiItemInfoWH40KIcon ); if ( UsingEDBSystem() > 0 ) { @@ -4244,6 +4304,13 @@ 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 + { + //////////////// POISON PERCENTAGE + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWH40KIcon, 6, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + } DrawSecondaryStats( gpItemDescObject ); } @@ -5130,6 +5197,25 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) } } } + + // Flugente Zombies + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) + { + if(!cnt) cnt += 2; + + ++cnt; // for the new index-line + + ///////////////////// poison percentage + // only draw if item is poisoned in any way + if ( Item[gpItemDescObject->usItem].bPoisonPercentage != 0 || (Item[gpItemDescObject->usItem].usItemClass == IC_GUN && AmmoTypes[Magazine[ Item[ gpItemDescObject->usItem ].ubClassIndex].ubAmmoType].poisonPercentage != 0 ) ) + { + if (cnt >= sFirstLine && cnt < sLastLine) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWH40KIcon, 6, gItemDescAdvRegions[cnt-sFirstLine][0].sLeft + sOffsetX, gItemDescAdvRegions[cnt-sFirstLine][0].sTop + sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + cnt++; + } + } } void DrawMiscStats( OBJECTTYPE * gpItemDescObject ) @@ -7246,7 +7332,6 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); mprintf( usX, usY, pStr ); } - } if (gubDescBoxPage == 2) { @@ -7511,6 +7596,59 @@ void DrawAmmoValues( OBJECTTYPE * gpItemDescObject, int shotsLeft ) FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); mprintf( usX, usY, pStr ); } + + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) // Flugente Zombies + { + ///////////////////// POISON PERCENTAGE + // Set line to draw into + ubNumLine = 4; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // base modificator + INT16 basevalue = AmmoTypes[Magazine[ Item[ gpItemDescObject->usItem ].ubClassIndex].ubAmmoType].poisonPercentage; + INT16 modificator = 0; // Does not exist (yet?) + INT16 finalvalue = basevalue - modificator; + + // Print base value + SetFontForeground( 5 ); + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + swprintf( pStr, L"%d", basevalue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // modifier + SetFontForeground( 5 ); + if ( modificator > 0 ) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + swprintf( pStr, L"%d", modificator ); + } + else if ( modificator < 0 ) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + swprintf( pStr, L"%d", modificator ); + } + else + { + swprintf( pStr, L"--" ); + } + + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print final value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", finalvalue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } } else if (gubDescBoxPage == 2) { @@ -10660,47 +10798,6 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } cnt++; } - - ///////////////////// MAX COUNTER FORCE - iFloatModifier[0] = CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_STAND ); - iFloatModifier[1] = CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_CROUCH ); - iFloatModifier[2] = CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_PRONE ); - if ((iFloatModifier[0] != 0 || iFloatModifier[1] != 0 || iFloatModifier[2] != 0) && UsingNewCTHSystem() == true && Item[gpItemDescObject->usItem].usItemClass == IC_GUN ) - { - if (cnt >= sFirstLine && cnt < sLastLine) - { - // Set Y coordinates - sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; - sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - - // Print Values - for (UINT8 cnt2 = 0; cnt2 < 3; cnt2++) - { - SetFontForeground( 5 ); - sLeft = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sLeft; - sWidth = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sRight - sLeft; - if (iFloatModifier[cnt2] > 0) - { - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - swprintf( pStr, L"%3.1f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - } - else if (iFloatModifier[cnt2] < 0) - { - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - swprintf( pStr, L"%3.1f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - } - else - { - swprintf( pStr, L"--" ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - } - mprintf( usX, usY, pStr ); - } - } - cnt++; - } // HEADROCK HAM 5: Counter-Force Frequency has been removed from the game in favour of a more realistic system. /* @@ -10783,7 +10880,7 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - iFloatModifier[0] = Weapon[ Item[gpItemDescObject->usItem].ubClassIndex].usOverheatingSingleShotTemperature; + iFloatModifier[0] = Weapon[ gpItemDescObject->usItem ].usOverheatingSingleShotTemperature; iFloatModifier[1] = GetSingleShotTemperature( gpItemDescObject ) - iFloatModifier[0]; iFloatModifier[2] = GetSingleShotTemperature( gpItemDescObject ); @@ -10890,7 +10987,7 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - iFloatModifier[0] = Weapon[ Item[gpItemDescObject->usItem].ubClassIndex].usOverheatingJamThreshold; + iFloatModifier[0] = Weapon[ gpItemDescObject->usItem ].usOverheatingJamThreshold; iFloatModifier[1] = GetOverheatJamThreshold(gpItemDescObject) - iFloatModifier[0]; iFloatModifier[2] = GetOverheatJamThreshold(gpItemDescObject); @@ -10943,7 +11040,7 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - iFloatModifier[0] = Weapon[ Item[gpItemDescObject->usItem].ubClassIndex].usOverheatingDamageThreshold; + iFloatModifier[0] = Weapon[ gpItemDescObject->usItem ].usOverheatingDamageThreshold; iFloatModifier[1] = GetOverheatDamageThreshold(gpItemDescObject) - iFloatModifier[0]; iFloatModifier[2] = GetOverheatDamageThreshold(gpItemDescObject); @@ -11070,11 +11167,14 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } cnt++; } + } + + // Flugente Zombies + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) + { + ///////////////////////////////////////////////////// INDEXES + if(!cnt) cnt += 2; - // various overheating modificators - if ( ( Item[gpItemDescObject->usItem].overheatTemperatureModificator != 0.0 ) || ( Item[gpItemDescObject->usItem].overheatCooldownModificator != 0.0 ) || ( Item[gpItemDescObject->usItem].overheatJamThresholdModificator != 0.0 ) || ( Item[gpItemDescObject->usItem].overheatDamageThresholdModificator != 0.0 ) ) - { - ///////////////////////////////////////////////////// INDEXES if (cnt >= sFirstLine && cnt < sLastLine) { SetFontForeground( FONT_MCOLOR_WHITE ); @@ -11099,31 +11199,32 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } ++cnt; - ///////////////////// TEMPERATURE MODIFICATOR - iFloatModifier[0] = Item[gpItemDescObject->usItem].overheatTemperatureModificator; - iFloatModifier[1] = 0.0; // doesn't exist (yet?) - iFloatModifier[2] = iFloatModifier[0]; + ///////////////////// poison percentage + iModifier[0] = Item[gpItemDescObject->usItem].bPoisonPercentage; - if ( iFloatModifier[0] != 0 ) + UINT8 ammotype = (*gpItemDescObject)[0]->data.gun.ubGunAmmoType; // ... get type of ammunition used ... + + iModifier[1] = AmmoTypes[ammotype].poisonPercentage; + iModifier[2] = iModifier[0] + iModifier[1]; + + // only draw if item is poisoned in any way + if ( iModifier[0] != 0 || iModifier[1] != 0 || iModifier[2] != 0 ) { if (cnt >= sFirstLine && cnt < sLastLine) { // Set Y coordinates sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - + // Print Values for (UINT8 cnt2 = 0; cnt2 < 3; cnt2++) { - SetFontForeground( 5 ); + SetRGBFontForeground( 0, 255, 0 ); sLeft = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sLeft; sWidth = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sRight - sLeft; - if (iFloatModifier[cnt2] > 0) + if (iModifier[cnt2] > 0) { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); + swprintf( pStr, L"%d", iModifier[cnt2] ); FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); #ifdef CHINESE wcscat( pStr, ChineseSpecString1 ); @@ -11133,10 +11234,7 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } else if (iFloatModifier[cnt2] < 0) { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); + swprintf( pStr, L"%d", iModifier[cnt2] ); FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); #ifdef CHINESE wcscat( pStr, ChineseSpecString1 ); @@ -11154,176 +11252,6 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } cnt++; } - - ///////////////////// COOLDOWN FACTOR MODIFICATOR - iFloatModifier[0] = Item[gpItemDescObject->usItem].overheatCooldownModificator; - iFloatModifier[1] = 0.0; // doesn't exist (yet?) - iFloatModifier[2] = iFloatModifier[0]; - - if ( iFloatModifier[0] != 0 ) - { - if (cnt >= sFirstLine && cnt < sLastLine) - { - // Set Y coordinates - sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; - sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - - // Print Values - for (UINT8 cnt2 = 0; cnt2 < 3; cnt2++) - { - SetFontForeground( 5 ); - sLeft = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sLeft; - sWidth = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sRight - sLeft; - if (iFloatModifier[cnt2] > 0) - { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - #ifdef CHINESE - wcscat( pStr, ChineseSpecString1 ); - #else - wcscat( pStr, L"%" ); - #endif - } - else if (iFloatModifier[cnt2] < 0) - { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - #ifdef CHINESE - wcscat( pStr, ChineseSpecString1 ); - #else - wcscat( pStr, L"%" ); - #endif - } - else - { - swprintf( pStr, L"--" ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - } - mprintf( usX, usY, pStr ); - } - } - cnt++; - } - - ///////////////////// JAM THRESHOLD FACTOR MODIFICATOR - iFloatModifier[0] = Item[gpItemDescObject->usItem].overheatJamThresholdModificator; - iFloatModifier[1] = 0.0; // doesn't exist (yet?) - iFloatModifier[2] = iFloatModifier[0]; - - if ( iFloatModifier[0] != 0 ) - { - if (cnt >= sFirstLine && cnt < sLastLine) - { - // Set Y coordinates - sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; - sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - - // Print Values - for (UINT8 cnt2 = 0; cnt2 < 3; cnt2++) - { - SetFontForeground( 5 ); - sLeft = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sLeft; - sWidth = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sRight - sLeft; - if (iFloatModifier[cnt2] > 0) - { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - #ifdef CHINESE - wcscat( pStr, ChineseSpecString1 ); - #else - wcscat( pStr, L"%" ); - #endif - } - else if (iFloatModifier[cnt2] < 0) - { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - #ifdef CHINESE - wcscat( pStr, ChineseSpecString1 ); - #else - wcscat( pStr, L"%" ); - #endif - } - else - { - swprintf( pStr, L"--" ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - } - mprintf( usX, usY, pStr ); - } - } - cnt++; - } - - ///////////////////// DAMAGE THRESHOLD MODIFICATOR - iFloatModifier[0] = Item[gpItemDescObject->usItem].overheatDamageThresholdModificator; - iFloatModifier[1] = 0.0; // doesn't exist (yet?) - iFloatModifier[2] = iFloatModifier[0]; - - if ( iFloatModifier[0] != 0 ) - { - if (cnt >= sFirstLine && cnt < sLastLine) - { - // Set Y coordinates - sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; - sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; - - // Print Values - for (UINT8 cnt2 = 0; cnt2 < 3; cnt2++) - { - SetFontForeground( 5 ); - sLeft = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sLeft; - sWidth = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sRight - sLeft; - if (iFloatModifier[cnt2] > 0) - { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - #ifdef CHINESE - wcscat( pStr, ChineseSpecString1 ); - #else - wcscat( pStr, L"%" ); - #endif - } - else if (iFloatModifier[cnt2] < 0) - { - if ( cnt2 == 1 ) - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - - swprintf( pStr, L"%4.2f", iFloatModifier[cnt2] ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - #ifdef CHINESE - wcscat( pStr, ChineseSpecString1 ); - #else - wcscat( pStr, L"%" ); - #endif - } - else - { - swprintf( pStr, L"--" ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - } - mprintf( usX, usY, pStr ); - } - } - cnt++; - } - } - } } diff --git a/Tactical/Interface Panels.cpp b/Tactical/Interface Panels.cpp index 8e3f4c6a..82422417 100644 --- a/Tactical/Interface Panels.cpp +++ b/Tactical/Interface Panels.cpp @@ -2928,8 +2928,17 @@ void RenderSMPanel( BOOLEAN *pfDirty ) else { GetMoraleString( gpSMCurrentMerc, pMoraleStr ); - swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr ); + // Flugente: added a display for poison, only show text if actualy poisoned + if ( gpSMCurrentMerc->bPoisonSum > 0 ) + { + swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bPoisonSum, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr ); + } + else + { + swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], gpSMCurrentMerc->stats.bLife, gpSMCurrentMerc->stats.bLifeMax, gpSMCurrentMerc->bBreath, gpSMCurrentMerc->bBreathMax, pMoraleStr ); + } SetRegionFastHelpText( &(gSM_SELMERCBarsRegion), pStr ); + } } else @@ -5442,16 +5451,25 @@ void RenderTEAMPanel( BOOLEAN fDirty ) } else { - GetMoraleString( pSoldier, pMoraleStr ); - swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr ); - SetRegionFastHelpText( &(gTEAM_BarsRegions[ cnt ]), pStr ); + GetMoraleString( pSoldier, pMoraleStr ); + + // Flugente: added a display for poison, only show text if actually poisoned + if ( pSoldier->bPoisonSum > 0 ) + { + swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_WITH_POISON_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bPoisonSum, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr ); + } + else + { + swprintf( pStr, TacticalStr[ MERC_VITAL_STATS_POPUPTEXT ], pSoldier->stats.bLife, pSoldier->stats.bLifeMax, pSoldier->bBreath, pSoldier->bBreathMax, pMoraleStr ); + } + SetRegionFastHelpText( &(gTEAM_BarsRegions[ cnt ]), pStr ); } - } - else - { - SetRegionFastHelpText( &(gTEAM_BarsRegions[ cnt ]), L"" ); - } } + else + { + SetRegionFastHelpText( &(gTEAM_BarsRegions[ cnt ]), L"" ); + } + } if ( !( pSoldier->flags.uiStatusFlags & SOLDIER_DEAD ) ) { diff --git a/Tactical/Interface Utils.cpp b/Tactical/Interface Utils.cpp index e55f1eb6..d11ad147 100644 --- a/Tactical/Interface Utils.cpp +++ b/Tactical/Interface Utils.cpp @@ -24,22 +24,30 @@ #endif #define LIFE_BAR_SHADOW FROMRGB( 108, 12, 12 ) -#define LIFE_BAR FROMRGB( 200, 0, 0 ) +#define LIFE_BAR FROMRGB( 200, 0, 0 ) #define BANDAGE_BAR_SHADOW FROMRGB( 156, 60, 60 ) -#define BANDAGE_BAR FROMRGB( 222, 132, 132 ) +#define BANDAGE_BAR FROMRGB( 222, 132, 132 ) #define BLEEDING_BAR_SHADOW FROMRGB( 128, 128, 60 ) -#define BLEEDING_BAR FROMRGB( 240, 240, 20 ) -#define CURR_BREATH_BAR_SHADOW FROMRGB( 8, 12, 118 ) // the MAX max breatth, always at 100% -#define CURR_BREATH_BAR FROMRGB( 8, 12, 160 ) -#define CURR_MAX_BREATH FROMRGB( 0, 0, 0 ) // the current max breath, black -#define CURR_MAX_BREATH_SHADOW FROMRGB( 0, 0, 0 ) -#define MORALE_BAR_SHADOW FROMRGB( 8, 112, 12 ) -#define MORALE_BAR FROMRGB( 8, 180, 12 ) -#define BREATH_BAR_SHADOW FROMRGB( 60, 108, 108 ) // the lt blue current breath -#define BREATH_BAR FROMRGB( 113, 178, 218 ) +#define BLEEDING_BAR FROMRGB( 240, 240, 20 ) + +#define POISON_LIFE_BAR_SHADOW FROMRGB( 12, 108, 12 ) +#define POISON_LIFE_BAR FROMRGB( 0, 200, 0 ) +#define POISON_BANDAGE_BAR_SHADOW FROMRGB( 80, 186, 80 ) +#define POISON_BANDAGE_BAR FROMRGB( 152, 252, 152 ) +#define POISON_BLEEDING_BAR_SHADOW FROMRGB( 128, 60, 128 ) +#define POISON_BLEEDING_BAR FROMRGB( 240, 20, 240 ) + +#define CURR_BREATH_BAR_SHADOW FROMRGB( 8, 12, 118 ) // the MAX max breatth, always at 100% +#define CURR_BREATH_BAR FROMRGB( 8, 12, 160 ) +#define CURR_MAX_BREATH FROMRGB( 0, 0, 0 ) // the current max breath, black +#define CURR_MAX_BREATH_SHADOW FROMRGB( 0, 0, 0 ) +#define MORALE_BAR_SHADOW FROMRGB( 8, 112, 12 ) +#define MORALE_BAR FROMRGB( 8, 180, 12 ) +#define BREATH_BAR_SHADOW FROMRGB( 60, 108, 108 ) // the lt blue current breath +#define BREATH_BAR FROMRGB( 113, 178, 218 ) #define BREATH_BAR_SHAD_BACK FROMRGB( 1,1,1 ) -#define FACE_WIDTH 48 -#define FACE_HEIGHT 43 +#define FACE_WIDTH 48 +#define FACE_HEIGHT 43 // backgrounds for breath max background @@ -162,12 +170,15 @@ void DrawLifeUIBarEx( SOLDIERTYPE *pSoldier, INT16 sXPos, INT16 sYPos, INT16 sWi pDestBuf = LockVideoSurface( uiBuffer, &uiDestPitchBYTES ); SetClippingRegionAndImageWidth( uiDestPitchBYTES, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); - + dStart = sYPos; + dEnd = 0; + // FIRST DO MAX LIFE dPercentage = (FLOAT)pSoldier->stats.bLife / (FLOAT)100; + FLOAT dstart1 = dStart - dEnd; + dStart = (FLOAT)( dStart - dEnd ); dEnd = dPercentage * sHeight; - dStart = sYPos; - + usLineColor = Get16BPPColor( LIFE_BAR_SHADOW ); RectangleDraw( TRUE, sXPos, (INT32)dStart, sXPos, (INT32)( dStart - dEnd ) , usLineColor, pDestBuf ); @@ -177,15 +188,33 @@ void DrawLifeUIBarEx( SOLDIERTYPE *pSoldier, INT16 sXPos, INT16 sYPos, INT16 sWi usLineColor = Get16BPPColor( LIFE_BAR_SHADOW ); RectangleDraw( TRUE, sXPos+ 2, (INT32)dStart, sXPos + 2, (INT32)( dStart - dEnd ), usLineColor, pDestBuf ); - // NOW DO BANDAGE + // poisoned life first + if ( pSoldier->bPoisonLife ) + { + dPercentage = (FLOAT)pSoldier->bPoisonLife / (FLOAT)100; + FLOAT dpoisonStart = (FLOAT)( dstart1 ); + FLOAT dpoisonEnd = dPercentage * sHeight; + + usLineColor = Get16BPPColor( POISON_LIFE_BAR_SHADOW ); + RectangleDraw( TRUE, sXPos, (INT32)dpoisonStart, sXPos, (INT32)( dpoisonStart - dpoisonEnd ) , usLineColor, pDestBuf ); + usLineColor = Get16BPPColor( POISON_LIFE_BAR ); + RectangleDraw( TRUE, sXPos+ 1, (INT32)dpoisonStart, sXPos + 1, (INT32)( dpoisonStart - dpoisonEnd ), usLineColor, pDestBuf ); + + usLineColor = Get16BPPColor( POISON_LIFE_BAR_SHADOW ); + RectangleDraw( TRUE, sXPos+ 2, (INT32)dpoisonStart, sXPos + 2, (INT32)( dpoisonStart - dpoisonEnd ), usLineColor, pDestBuf ); + } + + // NOW DO BANDAGE + // Calculate bandage + FLOAT dstart2 = dStart - dEnd; bBandage = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding; if ( bBandage ) { dPercentage = (FLOAT)bBandage / (FLOAT)100; - dStart = (FLOAT)( sYPos - dEnd ); + dStart = (FLOAT)( dStart - dEnd ); dEnd = ( dPercentage * sHeight ); usLineColor = Get16BPPColor( BANDAGE_BAR_SHADOW ); @@ -198,7 +227,26 @@ void DrawLifeUIBarEx( SOLDIERTYPE *pSoldier, INT16 sXPos, INT16 sYPos, INT16 sWi RectangleDraw( TRUE, sXPos+ 2, (INT32)dStart, sXPos + 2, (INT32)( dStart - dEnd ), usLineColor, pDestBuf ); } + // get amount of poisoned bandage + INT8 bPoisonBandage = pSoldier->bPoisonSum - pSoldier->bPoisonBleeding - pSoldier->bPoisonLife; + if ( bPoisonBandage ) + { + dPercentage = (FLOAT)bPoisonBandage / (FLOAT)100; + FLOAT dpoisonStart = (FLOAT)( dstart2 ); + FLOAT dpoisonEnd = dPercentage * sHeight; + + usLineColor = Get16BPPColor( POISON_BANDAGE_BAR_SHADOW ); + RectangleDraw( TRUE, sXPos, (INT32)dpoisonStart, sXPos, (INT32)( dpoisonStart - dpoisonEnd ) , usLineColor, pDestBuf ); + + usLineColor = Get16BPPColor( POISON_BANDAGE_BAR ); + RectangleDraw( TRUE, sXPos+ 1, (INT32)dpoisonStart, sXPos + 1, (INT32)( dpoisonStart - dpoisonEnd ), usLineColor, pDestBuf ); + + usLineColor = Get16BPPColor( POISON_BANDAGE_BAR_SHADOW ); + RectangleDraw( TRUE, sXPos+ 2, (INT32)dpoisonStart, sXPos + 2, (INT32)( dpoisonStart - dpoisonEnd ), usLineColor, pDestBuf ); + } + // NOW DO BLEEDING + FLOAT dstart3 = dStart - dEnd; if ( pSoldier->bBleeding ) { dPercentage = (FLOAT)pSoldier->bBleeding / (FLOAT)100; @@ -215,6 +263,23 @@ void DrawLifeUIBarEx( SOLDIERTYPE *pSoldier, INT16 sXPos, INT16 sYPos, INT16 sWi RectangleDraw( TRUE, sXPos+ 2, (INT32)dStart, sXPos + 2, (INT32)( dStart - dEnd ), usLineColor, pDestBuf ); } + // poisoned bleeding + if ( pSoldier->bPoisonBleeding ) + { + dPercentage = (FLOAT)pSoldier->bPoisonBleeding / (FLOAT)100; + FLOAT dpoisonStart = (FLOAT)( dstart3 ); + FLOAT dpoisonEnd = dPercentage * sHeight; + + usLineColor = Get16BPPColor( POISON_BLEEDING_BAR_SHADOW ); + RectangleDraw( TRUE, sXPos, (INT32)dpoisonStart, sXPos, (INT32)( dpoisonStart - dpoisonEnd ) , usLineColor, pDestBuf ); + + usLineColor = Get16BPPColor( POISON_BLEEDING_BAR ); + RectangleDraw( TRUE, sXPos+ 1, (INT32)dpoisonStart, sXPos + 1, (INT32)( dpoisonStart - dpoisonEnd ), usLineColor, pDestBuf ); + + usLineColor = Get16BPPColor( POISON_BLEEDING_BAR_SHADOW ); + RectangleDraw( TRUE, sXPos+ 2, (INT32)dpoisonStart, sXPos + 2, (INT32)( dpoisonStart - dpoisonEnd ), usLineColor, pDestBuf ); + } + UnLockVideoSurface( uiBuffer ); } diff --git a/Tactical/Interface.cpp b/Tactical/Interface.cpp index 749146dc..cf641ddb 100644 --- a/Tactical/Interface.cpp +++ b/Tactical/Interface.cpp @@ -2210,6 +2210,21 @@ void DrawSelectedUIAboveGuy( UINT16 usSoldierID ) } } } + + if ( pSoldier->IsZombie() ) + { + swprintf(NameStr, pSoldier->name); + + // Display name + SetFont( TINYFONT1 ); + SetFontBackground( FONT_MCOLOR_BLACK ); + SetFontForeground( FONT_MCOLOR_WHITE ); + + //legion2 + FindFontCenterCoordinates( sXPos, (INT16)( sYPos -10 ), (INT16)(80 ), 1, NameStr, TINYFONT1, &sX, &sY ); + gprintfdirty( sX, sY, NameStr ); + mprintf( sX, sY, NameStr ); + } //------------ } } @@ -3162,6 +3177,7 @@ void DrawBarsInUIBox( SOLDIERTYPE *pSoldier , INT16 sXPos, INT16 sYPos, INT16 sW UINT8 *pDestBuf; UINT16 usLineColor; INT8 bBandage; + INT8 bPoisonBandage; // Draw breath points @@ -3174,6 +3190,8 @@ void DrawBarsInUIBox( SOLDIERTYPE *pSoldier , INT16 sXPos, INT16 sYPos, INT16 sW // get amt bandaged bBandage = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding; + // get amount of poisoned bandage + bPoisonBandage = pSoldier->bPoisonSum - pSoldier->bPoisonBleeding - pSoldier->bPoisonLife; // NOW DO BLEEDING @@ -3194,6 +3212,23 @@ void DrawBarsInUIBox( SOLDIERTYPE *pSoldier , INT16 sXPos, INT16 sYPos, INT16 sW } } + // poisoned bleeding + if ( pSoldier->bPoisonBleeding ) + { + dPercentage = (FLOAT)( pSoldier->stats.bLifeMax - pSoldier->bBleeding + pSoldier->bPoisonBleeding )/ (FLOAT)100; + dWidth = dPercentage * sWidth; + if(gbPixelDepth==16) + { + usLineColor = Get16BPPColor( FROMRGB( 240, 20, 240 ) ); + RectangleDraw( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); + } + else if(gbPixelDepth==8) + { + // DB Need to change this to a color from the 8-bit standard palette + usLineColor = COLOR_GREEN; + RectangleDraw8( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); + } + } if( bBandage ) { @@ -3212,6 +3247,23 @@ void DrawBarsInUIBox( SOLDIERTYPE *pSoldier , INT16 sXPos, INT16 sYPos, INT16 sW } } + // poisoned bandage + if ( bPoisonBandage ) + { + dPercentage = (FLOAT)( pSoldier->stats.bLife + bPoisonBandage )/ (FLOAT)100; + dWidth = dPercentage * sWidth; + if(gbPixelDepth==16) + { + usLineColor = Get16BPPColor( FROMRGB( 132, 222, 132 ) ); + RectangleDraw( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); + } + else if(gbPixelDepth==8) + { + // DB Need to change this to a color from the 8-bit standard palette + usLineColor = COLOR_GREEN; + RectangleDraw8( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); + } + } dPercentage = (FLOAT)pSoldier->stats.bLife / (FLOAT)100; dWidth = dPercentage * sWidth; @@ -3227,6 +3279,23 @@ void DrawBarsInUIBox( SOLDIERTYPE *pSoldier , INT16 sXPos, INT16 sYPos, INT16 sW RectangleDraw8( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); } + // poisoned life + if ( pSoldier->bPoisonLife ) + { + dPercentage = (FLOAT)( pSoldier->bPoisonLife )/ (FLOAT)100; + dWidth = dPercentage * sWidth; + if(gbPixelDepth==16) + { + usLineColor = Get16BPPColor( FROMRGB( 0, 200, 0 ) ); + RectangleDraw( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); + } + else if(gbPixelDepth==8) + { + // DB Need to change this to a color from the 8-bit standard palette + usLineColor = COLOR_GREEN; + RectangleDraw8( TRUE, sXPos + 3, sYPos + 1, (INT32)( sXPos + dWidth + 3 ), sYPos + 1, usLineColor, pDestBuf ); + } + } dPercentage = (FLOAT)( pSoldier->bBreathMax ) / (FLOAT)100; diff --git a/Tactical/Inventory Choosing.cpp b/Tactical/Inventory Choosing.cpp index 8e89cf30..3a8ac7e4 100644 --- a/Tactical/Inventory Choosing.cpp +++ b/Tactical/Inventory Choosing.cpp @@ -846,6 +846,10 @@ void GenerateRandomEquipment( SOLDIERCREATE_STRUCT *pp, INT8 bSoldierClass, INT8 } } break; + + case SOLDIER_CLASS_ZOMBIE: + // zombies get no items at all... + break; } for ( i = 0; i < pp->Inv.size(); i++ ) @@ -1052,6 +1056,8 @@ void ChooseWeaponForSoldierCreateStruct( SOLDIERCREATE_STRUCT *pp, INT8 bWeaponC bStatus += (INT8)(HighestPlayerProgressPercentage() / 10); bStatus = (INT8)min( 100, bStatus ); break; + case SOLDIER_CLASS_ZOMBIE: + bStatus = (INT8)(1); // if a zombie gets anything, it'll be broken default: bStatus = (INT8)(50 + Random( 51 ) ); break; @@ -2105,6 +2111,7 @@ void ChooseFaceGearForSoldierCreateStruct( SOLDIERCREATE_STRUCT *pp ) break; case SOLDIER_CLASS_ADMINISTRATOR: case SOLDIER_CLASS_GREEN_MILITIA: + case SOLDIER_CLASS_ZOMBIE: break; } } diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index 1e4ea1da..e2acec7d 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -219,6 +219,9 @@ typedef enum #define GS_CARTRIDGE_IN_CHAMBER 0x01 #define GS_WEAPON_BEING_RELOADED 0x02 +// Flugente: define for maximum temperature +#define OVERHEATING_MAX_TEMPERATURE 60000.0 + //forward declaration class OBJECTTYPE; class SOLDIERTYPE; @@ -956,6 +959,9 @@ typedef struct BOOLEAN blockironsight; // if a gun or any attachment have this property, the iron sight won't be usable (if there is at least one other usable sight) + // Flugente poison system + INT16 bPoisonPercentage; + } INVTYPE; // CHRISL: Added new structures to handle LBE gear and the two new XML files that will be needed to deal diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index 3be0526c..5c92ca0b 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -3042,7 +3042,7 @@ UINT32 CalculateCarriedWeight( SOLDIERTYPE * pSoldier ) UINT32 uiTotalWeight = 0; UINT32 uiPercent; UINT8 ubLoop; - UINT8 ubStrengthForCarrying; + UINT16 ubStrengthForCarrying; //Pulmu: Changes for dynamic ammo weight for( ubLoop = 0; ubLoop < pSoldier->inv.size(); ubLoop++) @@ -7418,7 +7418,7 @@ BOOLEAN CreateGun( UINT16 usItem, INT16 bStatus, OBJECTTYPE * pObj ) pStackedObject->data.ubImprintID = NO_PROFILE; // Flugente FTW 1: temperature on creation is 0 - pStackedObject->data.bTemperature = 0.0; + pStackedObject->data.bTemperature = 0.0f; if (Weapon[ usItem ].ubWeaponClass == MONSTERCLASS) { @@ -7533,6 +7533,7 @@ BOOLEAN CreateItem( UINT16 usItem, INT16 bStatus, OBJECTTYPE * pObj ) { (*pObj)[0]->data.objectStatus = bStatus; } + //ADB ubWeight has been removed, see comments in OBJECTTYPE //pObj->ubWeight = CalculateObjectWeight( pObj ); fRet = TRUE; @@ -9189,6 +9190,41 @@ void ActivateXRayDevice( SOLDIERTYPE * pSoldier ) pSoldier->uiXRayActivatedTime = GetWorldTotalSeconds(); } +void TurnOnXRayEffects( SOLDIERTYPE * pSoldier ) +{ + SOLDIERTYPE * pSoldier2; + UINT32 uiSlot; + + // first, scan through all mercs and turn off xrayed flag for anyone + // previously xrayed by this guy + for ( uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot ) + { + pSoldier2 = MercSlots[ uiSlot ]; + if ( pSoldier2 ) + { + if ( (pSoldier2->ubMiscSoldierFlags & SOLDIER_MISC_XRAYED) && (pSoldier2->aiData.ubXRayedBy == pSoldier->ubID) ) + { + pSoldier2->ubMiscSoldierFlags &= (~SOLDIER_MISC_XRAYED); + pSoldier2->aiData.ubXRayedBy = NOBODY; + } + } + } + // now turn on xray for anyone within range + for ( uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot ) + { + pSoldier2 = MercSlots[ uiSlot ]; + if ( pSoldier2 ) + { + if ( pSoldier2->bTeam != pSoldier->bTeam && PythSpacesAway( pSoldier->sGridNo, pSoldier2->sGridNo ) < XRAY_RANGE ) + { + pSoldier2->ubMiscSoldierFlags |= SOLDIER_MISC_XRAYED; + pSoldier2->aiData.ubXRayedBy = pSoldier->ubID; + } + } + } + pSoldier->uiXRayActivatedTime = GetWorldTotalSeconds(); +} + void TurnOffXRayEffects( SOLDIERTYPE * pSoldier ) { SOLDIERTYPE * pSoldier2; @@ -9453,13 +9489,17 @@ INT16 GetGearAPBonus( SOLDIERTYPE * pSoldier ) { INT16 bonus=0; - for (int j = HELMETPOS; j <= HEAD2POS; j++) + for (int j = HELMETPOS; j <= HEAD2POS; ++j) { - if (pSoldier->inv[j].exists() == true) { - bonus += Item[pSoldier->inv[j].usItem].APBonus; + if (pSoldier->inv[j].exists() == true) + { OBJECTTYPE* pObj = &pSoldier->inv[j]; - for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { - if(iter->exists()){ + + attachmentList::iterator iterend = (*pObj)[0]->attachments.begin(); + for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter) + { + if(iter->exists()) + { bonus += Item[iter->usItem].APBonus; } } @@ -11020,7 +11060,9 @@ BOOLEAN HasThermalOptics( SOLDIERTYPE * pSoldier ) if (!IsWeapon(pSoldier->inv[i].usItem) || (IsWeapon(pSoldier->inv[i].usItem) && usingGunScope == true) ) { if (Item[pSoldier->inv[i].usItem].thermaloptics) + { return TRUE; + } } } } @@ -13414,6 +13456,8 @@ BOOLEAN HasAttachmentOfClass( OBJECTTYPE * pObj, UINT32 aFlag ) return( FALSE ); } + +extern void HandleSight(SOLDIERTYPE *pSoldier, UINT8 ubSightFlags); /////////////////////////////////////////////////////////////////////////////////////////////////// // HEADROCK HAM 5: Item Transformation. // Item Transformation is a new way to interact with items. Where Merges combine two items into one, a Transformation @@ -13816,4 +13860,3 @@ UINT64 GetAvailableAttachmentPoint (OBJECTTYPE * pObject, UINT8 subObject) return point; } - diff --git a/Tactical/Items.h b/Tactical/Items.h index 45d375a0..6efda2e0 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -225,6 +225,7 @@ void CheckEquipmentForFragileItemDamage( SOLDIERTYPE *pSoldier, INT32 iDamage ); #define XRAY_TIME 5 extern void ActivateXRayDevice( SOLDIERTYPE * pSoldier ); +extern void TurnOnXRayEffects( SOLDIERTYPE * pSoldier ); extern void TurnOffXRayEffects( SOLDIERTYPE * pSoldier ); OBJECTTYPE* FindLaunchableAttachment( OBJECTTYPE * pObj, UINT16 usWeapon ); UINT16 FindLegalGrenade(UINT16 usItem); @@ -448,7 +449,7 @@ BOOLEAN UseTotalMedicalKitPoints( SOLDIERTYPE * pSoldier, UINT16 usPointsToConsu FLOAT GetItemCooldownFactor( OBJECTTYPE * pObj ); void GetScopeLists( OBJECTTYPE * pObj, std::map& arScopeMap ); -// check if item belongs to a specific attachemnt class +// check if item belongs to a specific attachment class BOOLEAN IsAttachmentClass( UINT16 usItem, UINT32 aFlag ); BOOLEAN HasAttachmentOfClass( OBJECTTYPE * pObj, UINT32 aFlag ); @@ -459,6 +460,3 @@ bool IsAttachmentPointAvailable( UINT64 point, UINT32 attachmentID, BOOLEAN only UINT64 GetAvailableAttachmentPoint ( OBJECTTYPE * pObject, UINT8 subObject ); #endif - - - diff --git a/Tactical/Keys.cpp b/Tactical/Keys.cpp index 5c83116d..e8e68165 100644 --- a/Tactical/Keys.cpp +++ b/Tactical/Keys.cpp @@ -702,7 +702,7 @@ void HandleDoorTrap( SOLDIERTYPE * pSoldier, DOOR * pDoor ) DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("!!!!!!! Trap gone off %d", gTacticalStatus.ubAttackBusyCount) ); DebugAttackBusy( "!!!!!!! Trap gone off\n" ); - pSoldier->SoldierTakeDamage( 0, (UINT16) (10 + PreRandom( 10 )), (UINT16) ((3 + PreRandom( 3 ) * 1000)), TAKE_DAMAGE_ELECTRICITY, NOBODY, pDoor->sGridNo, 0, TRUE ); + pSoldier->SoldierTakeDamage( 0, (UINT16) (10 + PreRandom( 10 )), 0, (UINT16) ((3 + PreRandom( 3 ) * 1000)), TAKE_DAMAGE_ELECTRICITY, NOBODY, pDoor->sGridNo, 0, TRUE ); break; case SUPER_ELECTRIC: @@ -717,7 +717,7 @@ void HandleDoorTrap( SOLDIERTYPE * pSoldier, DOOR * pDoor ) DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("!!!!!!! Trap gone off %d", gTacticalStatus.ubAttackBusyCount) ); DebugAttackBusy( "!!!!!!! Trap gone off\n" ); - pSoldier->SoldierTakeDamage( 0, (UINT16) (20 + PreRandom( 20 )), (UINT16) ((6 + PreRandom( 6 ) * 1000)), TAKE_DAMAGE_ELECTRICITY, NOBODY, pDoor->sGridNo, 0, TRUE ); + pSoldier->SoldierTakeDamage( 0, (UINT16) (20 + PreRandom( 20 )), 0, (UINT16) ((6 + PreRandom( 6 ) * 1000)), TAKE_DAMAGE_ELECTRICITY, NOBODY, pDoor->sGridNo, 0, TRUE ); break; diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index 2dc32c79..278296f4 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -48,6 +48,7 @@ #include "WorldDat.h" // HEADROCK HAM 3.6: This must be included, for testing whether Bloodcats and Enemies can see one another. #include "Campaign Types.h" +#include "soldier tile.h" // added by Flugente //forward declarations of common classes to eliminate includes class OBJECTTYPE; diff --git a/Tactical/Militia Control.cpp b/Tactical/Militia Control.cpp index ac36ac3e..582ecff5 100644 --- a/Tactical/Militia Control.cpp +++ b/Tactical/Militia Control.cpp @@ -109,20 +109,6 @@ extern SOLDIERTYPE *pTMilitiaSoldier; extern BOOLEAN SoldierCanAffordNewStance( SOLDIERTYPE *pSoldier, UINT8 ubDesiredStance ); - - - - - - - - - - - - - - void ResetMilitia() { UINT8 ubNumGreen = 0; @@ -2370,5 +2356,3 @@ void MilitiaControlMenuBtnCallBack( MOUSE_REGION * pRegion, INT32 iReason ) // return; //} - - diff --git a/Tactical/Morale.cpp b/Tactical/Morale.cpp index 93d347b9..38914d7b 100644 --- a/Tactical/Morale.cpp +++ b/Tactical/Morale.cpp @@ -88,6 +88,8 @@ MoraleEvent gbMoraleEvent[NUM_MORALE_EVENTS] = { TACTICAL_MORALE_EVENT, -1}, // MORALE_PSYCHO_UNABLE_TO_PSYCHO, { STRATEGIC_MORALE_EVENT, +1}, // MORALE_PACIFIST_GAIN_NONCOMBAT, { TACTICAL_MORALE_EVENT, +1}, // MORALE_MALICIOUS_HIT, + // added by SANDRO + { TACTICAL_MORALE_EVENT, -1}, //MORALE_KHORNATE_RANGED_COMBAT, }; BOOLEAN gfSomeoneSaidMoraleQuote = FALSE; @@ -523,7 +525,7 @@ void UpdateSoldierMorale( SOLDIERTYPE * pSoldier, INT8 bMoraleEvent ) } } } - else if ( bMoraleMod != MORALE_PSYCHO_UNABLE_TO_PSYCHO) + else if ( bMoraleMod != MORALE_PSYCHO_UNABLE_TO_PSYCHO && bMoraleMod != MORALE_KHORNATE_RANGED_COMBAT) { switch( pProfile->bAttitude ) { @@ -909,6 +911,12 @@ void HandleMoraleEvent( SOLDIERTYPE *pSoldier, INT8 bMoraleEvent, INT16 sMapX, I HandleMoraleEventForSoldier( pSoldier, bMoraleEvent ); break; + // added by Flugente + case MORALE_KHORNATE_RANGED_COMBAT: + Assert( pSoldier ); + HandleMoraleEventForSoldier( pSoldier, bMoraleEvent ); + break; + default: // debug message ScreenMsg( MSG_FONT_RED, MSG_BETAVERSION, L"Invalid morale event type = %d. AM/CC-1", bMoraleEvent ); diff --git a/Tactical/Morale.h b/Tactical/Morale.h index 4e341d16..c9af71cd 100644 --- a/Tactical/Morale.h +++ b/Tactical/Morale.h @@ -49,6 +49,9 @@ typedef enum MORALE_PSYCHO_UNABLE_TO_PSYCHO, MORALE_PACIFIST_GAIN_NONCOMBAT, MORALE_MALICIOUS_HIT, + + // added by Flugente + MORALE_KHORNATE_RANGED_COMBAT, NUM_MORALE_EVENTS } MoraleEventNames; diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 6b9e5a34..e2ee0eee 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -6975,10 +6975,12 @@ BOOLEAN CheckForEndOfBattle( BOOLEAN fAnEnemyRetreated ) if (pTeamSoldier->iHealableInjury < 0) pTeamSoldier->iHealableInjury = 0; + pTeamSoldier->bPoisonLife = min(pTeamSoldier->bPoisonSum, pTeamSoldier->bPoisonLife + OKLIFE - pTeamSoldier->stats.bLife); pTeamSoldier->stats.bLife = OKLIFE; } pTeamSoldier->bBleeding = 0; // let's think, the autobandage was done for the militia too + pTeamSoldier->bPoisonBleeding = 0; } } gTacticalStatus.Team[ MILITIA_TEAM ].bAwareOfOpposition = FALSE; @@ -7255,6 +7257,26 @@ UINT8 NumEnemyInSector( ) } +UINT8 NumZombiesInSector( ) +{ + SOLDIERTYPE *pTeamSoldier; + INT32 cnt = 0; + UINT8 ubNumZombies = 0; + + for ( pTeamSoldier = Menptr, cnt = 0; cnt < TOTAL_SOLDIERS; ++pTeamSoldier, ++cnt ) + { + if ( pTeamSoldier->bActive && pTeamSoldier->bInSector && pTeamSoldier->stats.bLife > 0 ) + { + if ( pTeamSoldier->IsZombie() ) + { + ++ubNumZombies; + } + } + } + + return( ubNumZombies ); +} + UINT8 NumEnemyInSectorExceptCreatures() { SOLDIERTYPE *pTeamSoldier; @@ -7582,13 +7604,13 @@ BOOLEAN KillIncompacitatedEnemyInSector( ) // KIll...... // SANDRO - if the soldier is bleeding out, consider this damage as done by the last attacker if ( pTeamSoldier->ubAttackerID != NOBODY ) - pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 100, TAKE_DAMAGE_BLOODLOSS, pTeamSoldier->ubAttackerID, NOWHERE, 0, TRUE ); + pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 0, 100, TAKE_DAMAGE_BLOODLOSS, pTeamSoldier->ubAttackerID, NOWHERE, 0, TRUE ); else if ( pTeamSoldier->ubPreviousAttackerID != NOBODY ) - pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 100, TAKE_DAMAGE_BLOODLOSS, pTeamSoldier->ubPreviousAttackerID, NOWHERE, 0, TRUE ); + pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 0, 100, TAKE_DAMAGE_BLOODLOSS, pTeamSoldier->ubPreviousAttackerID, NOWHERE, 0, TRUE ); else if ( pTeamSoldier->ubNextToPreviousAttackerID != NOBODY ) - pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 100, TAKE_DAMAGE_BLOODLOSS, pTeamSoldier->ubNextToPreviousAttackerID, NOWHERE, 0, TRUE ); + pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 0, 100, TAKE_DAMAGE_BLOODLOSS, pTeamSoldier->ubNextToPreviousAttackerID, NOWHERE, 0, TRUE ); else - pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); + pTeamSoldier->SoldierTakeDamage( ANIM_CROUCH, pTeamSoldier->stats.bLife, 0, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); fReturnVal = TRUE; } @@ -7767,6 +7789,10 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: loop = %d, numslots = %d ",uiLoop, guiNumMercSlots)); pSoldier = MercSlots[uiLoop]; + // Flugente: zombies do not receive any suppression at all! + if ( pSoldier != NULL && pSoldier->IsZombie() ) + continue; + // Has this character received any Suppression Points since the last attack? // HEADROCK: Suppression Points accumulate by bullets flying near the character. It includes // friendly fire at a certain distance. As of HAM 3.2, it also happens with nearby explosions. diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index f5c13c3b..4fb1226d 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -393,4 +393,7 @@ BOOLEAN WillAirRaidBeStopped( INT16 sSectorX, INT16 sSectorY ); // HEADROCK HAM 3.5: Externalized for First Arrival enemy check extern UINT8 NumEnemyInSector(); + +// Flugente +extern UINT8 NumZombiesInSector(); #endif \ No newline at end of file diff --git a/Tactical/Rotting Corpses.cpp b/Tactical/Rotting Corpses.cpp index a15ced57..92000615 100644 --- a/Tactical/Rotting Corpses.cpp +++ b/Tactical/Rotting Corpses.cpp @@ -43,6 +43,9 @@ #include "strategic.h" #include "qarray.h" #include "Interface.h" + + #include "Campaign Types.h" + //#include "Queen Command.h" #endif #include "Animation Control.h" @@ -580,6 +583,10 @@ INT32 AddRottingCorpse( ROTTING_CORPSE_DEFINITION *pCorpseDef ) pCorpse->pAniTile->uiUserData = iIndex; pCorpse->iID = iIndex; + // copy name of corpse definition... + memcpy( &(pCorpse->name), &(pCorpseDef->name), sizeof(CHAR16) * 10 ); + pCorpse->name[9] = '\0'; + pCorpse->fActivated = TRUE; if ( Random( 100 ) > 50 ) @@ -853,6 +860,36 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE Corpse.usFlags |= ROTTING_CORPSE_USE_SNOW_CAMO_PALETTE; } + // Flugente Zombies: Determine if a zombie can rise from this corpse + switch ( gGameExternalOptions.sZombieRiseBehaviour ) + { + case 3: + // a zombie might rise again + if ( pSoldier->IsZombie() && Random( 2 ) > 0 ) + Corpse.usFlags |= ROTTING_CORPSE_NEVER_RISE_AGAIN; + break; + + case 2: + // death tosses a coin... + if ( Random( 2 ) > 0 ) + Corpse.usFlags |= ROTTING_CORPSE_NEVER_RISE_AGAIN; + break; + + case 1: + // no flag, the dead shall rise for eternity! + break; + + case 0: + default: + // a killed zombie won't rise again + if ( pSoldier->IsZombie() ) + Corpse.usFlags |= ROTTING_CORPSE_NEVER_RISE_AGAIN; + break; + } + + // Flugente: copy name of soldier... + memcpy( &(Corpse.name), &(pSoldier->name), sizeof(CHAR16) * 10 ); + Corpse.name[9] = '\0'; // Determine corpse type! ubType = (UINT8)gubAnimSurfaceCorpseID[ pSoldier->ubBodyType][ pSoldier->usAnimState ]; @@ -2046,3 +2083,364 @@ UINT8 GetNearestRottingCorpseAIWarning( INT32 sGridNo ) return( ubHighestWarning ); } + +// Flugente Zombies: resurrect zombies +void RaiseZombies( void ) +{ + if ( gGameSettings.fOptions[TOPTION_ZOMBIES] ) + { + // 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 ) + { + ROTTING_CORPSE * pCorpse; + BOOLEAN zombieshaverisen = FALSE; + + SECTORINFO *pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ]; + + for ( INT32 cnt = giNumRottingCorpse - 1; cnt >= 0; --cnt ) + { + if ( pSector->ubNumCreatures < gGameExternalOptions.ubGameMaximumNumberOfCreatures ) // ... if there is still room for more zombies (zombies count as creatures until a separate ZOMBIE_TEAM is implemented)... + { + 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->fActivated && pCorpse->def.fHeadTaken == FALSE ) // ... if corpse is active, and still has a head ... + { + 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 ); + + //++pSector->ubNumZombies; + //++pSector->ubZombiesInBattle; + + RemoveCorpse( cnt ); + } + } + } + } + } + } + } + else + { + // if there is no more room, we can skip this + break; + } + } + + if ( zombieshaverisen ) + { +#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 ); + } + } + } +} + +// 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 ) + { + 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; + + 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; + + // do not generate a new palette, thereby using the old palette of the corpse + MercCreateStruct.fNoGenNewPalette = TRUE; + + INT8 iNewIndex; + if ( TacticalCreateSoldier( &MercCreateStruct, (UINT8 *)&iNewIndex ) ) + { + /* certain values have to be set afterwards - the alternative 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); + ////////////////////////////////////////////////////////////////////// + + memcpy( &(pNewSoldier->name), &(pCorpse->name), sizeof(CHAR16) * 10 ); + pNewSoldier->name[9] = '\0'; + + // 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 ); + + + // 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: + ; + } + + return( canbezombie ); +} diff --git a/Tactical/Rotting Corpses.h b/Tactical/Rotting Corpses.h index 0bb587f8..bd7c7dc1 100644 --- a/Tactical/Rotting Corpses.h +++ b/Tactical/Rotting Corpses.h @@ -26,14 +26,14 @@ enum RottingCorpseDefines MMERC_FALL, MMERC_FALLF, - FMERC_JFK, - FMERC_BCK, - FMERC_FWD, + FMERC_JFK, // head has exploded + FMERC_BCK, // body lying backward + FMERC_FWD, // body lying forward FMERC_DHD, - FMERC_PRN, - FMERC_WTR, - FMERC_FALL, - FMERC_FALLF, + FMERC_PRN, // body lying on the belly while looking forward, one leg to the side + FMERC_WTR, // body lying in water + FMERC_FALL, // body lying backward, fallen from roof + FMERC_FALLF, // body lying forward, fallen from roof // CIVS M_DEAD1, @@ -73,17 +73,19 @@ enum RottingCorpseDefines -#define ROTTING_CORPSE_FIND_SWEETSPOT_FROM_GRIDNO 0x001 //Find the closest spot to the given gridno +#define ROTTING_CORPSE_FIND_SWEETSPOT_FROM_GRIDNO 0x001 //Find the closest spot to the given gridno #define ROTTING_CORPSE_USE_NORTH_ENTRY_POINT 0x002 //Find the spot closest to the north entry grid #define ROTTING_CORPSE_USE_SOUTH_ENTRY_POINT 0x004 //Find the spot closest to the south entry grid #define ROTTING_CORPSE_USE_EAST_ENTRY_POINT 0x008 //Find the spot closest to the east entry grid #define ROTTING_CORPSE_USE_WEST_ENTRY_POINT 0x010 //Find the spot closest to the west entry grid #define ROTTING_CORPSE_USE_CAMO_PALETTE 0x020 //We use cammo palette here.... -#define ROTTING_CORPSE_VEHICLE 0x040 //Vehicle Corpse -#define ROTTING_CORPSE_USE_STEALTH_PALETTE 0x080 //We use stealth palette here.... -#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_VEHICLE 0x040 //Vehicle Corpse +#define ROTTING_CORPSE_USE_STEALTH_PALETTE 0x080 //We use stealth palette here.... +#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 ) + typedef struct { @@ -94,26 +96,29 @@ typedef struct FLOAT dYPos; INT16 sHeightAdjustment; - PaletteRepID HeadPal; // Palette reps - PaletteRepID PantsPal; - PaletteRepID VestPal; - PaletteRepID SkinPal; + PaletteRepID HeadPal; // Palette reps + PaletteRepID PantsPal; + PaletteRepID VestPal; + PaletteRepID SkinPal; UINT8 ubDirection; - UINT32 uiTimeOfDeath; + UINT32 uiTimeOfDeath; - UINT16 usFlags; + UINT16 usFlags; INT8 bLevel; INT8 bVisible; INT8 bNumServicingCrows; UINT8 ubProfile; - BOOLEAN fHeadTaken; - UINT8 ubAIWarningValue; + BOOLEAN fHeadTaken; + UINT8 ubAIWarningValue; UINT8 ubFiller[ 12 ]; + // Flugente: added name so we can display individual name if corpse gets resurrected... + CHAR16 name[ 10 ]; + } ROTTING_CORPSE_DEFINITION; @@ -135,6 +140,9 @@ typedef struct BOOLEAN fAttractCrowsOnlyWhenOnScreen; INT32 iID; + // Flugente: added name so we can display individual name if corpse gets resurrected... + CHAR16 name[ 10 ]; + } ROTTING_CORPSE; @@ -192,4 +200,13 @@ INT32 GetGridNoOfCorpseGivenProfileID( UINT8 ubProfileID ); void DecayRottingCorpseAIWarnings( void ); UINT8 GetNearestRottingCorpseAIWarning( INT32 sGridNo ); +// Flugente: Raise zombies +void RaiseZombies( void ); + +// 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 ); + #endif \ No newline at end of file diff --git a/Tactical/SkillCheck.cpp b/Tactical/SkillCheck.cpp index e88986e4..75c9f01a 100644 --- a/Tactical/SkillCheck.cpp +++ b/Tactical/SkillCheck.cpp @@ -18,7 +18,7 @@ #include "Soldier Control.h" #endif -INT8 EffectiveStrength( SOLDIERTYPE * pSoldier ) +INT8 EffectiveStrength( SOLDIERTYPE *pSoldier) { INT8 bBandaged; INT32 iEffStrength; @@ -29,9 +29,9 @@ INT8 EffectiveStrength( SOLDIERTYPE * pSoldier ) bBandaged = pSoldier->stats.bLifeMax - pSoldier->stats.bLife - pSoldier->bBleeding; if (pSoldier->stats.bStrength > 0) - { - iEffStrength = pSoldier->stats.bStrength / 2; - iEffStrength += (pSoldier->stats.bStrength / 2) * (pSoldier->stats.bLife + bBandaged / 2) / (pSoldier->stats.bLifeMax); + { + iEffStrength = ( pSoldier->stats.bStrength )/ 2; + iEffStrength += ( (pSoldier->stats.bStrength ) / 2) * (pSoldier->stats.bLife + bBandaged / 2) / (pSoldier->stats.bLifeMax); } else { @@ -164,7 +164,7 @@ INT8 EffectiveExpLevel( SOLDIERTYPE * pSoldier ) iEffExpLevel -= 1; } } - + if (iEffExpLevel > 10) { // can't go over 10 - SANDRO @@ -476,7 +476,7 @@ INT32 SkillCheck( SOLDIERTYPE * pSoldier, INT8 bReason, INT8 bChanceMod ) case OPEN_WITH_CROWBAR: // Add for crowbar... iSkill = EffectiveStrength( pSoldier ) + 20; - fForceDamnSound = TRUE; + fForceDamnSound = TRUE; break; case SMASH_DOOR_CHECK: @@ -546,6 +546,14 @@ INT32 SkillCheck( SOLDIERTYPE * pSoldier, INT8 bReason, INT8 bChanceMod ) iSkill /= 2; } } + break; + case ATTACH_POWER_PACK: // added by Flugente + // technicians can attach it, other people can' + if ( HAS_SKILL_TRAIT( pSoldier, TECHNICIAN_NT )) + iSkill = 100; + else + iSkill = 0; + break; default: iSkill = 0; diff --git a/Tactical/SkillCheck.h b/Tactical/SkillCheck.h index f5416fa5..72d63fe5 100644 --- a/Tactical/SkillCheck.h +++ b/Tactical/SkillCheck.h @@ -41,6 +41,7 @@ enum SkillChecks ATTACHING_SPECIAL_ITEM_CHECK, ATTACHING_SPECIAL_ELECTRONIC_ITEM_CHECK, DISARM_ELECTRONIC_TRAP_CHECK, + ATTACH_POWER_PACK, // Flugente: attach a power pack to power armor } ; diff --git a/Tactical/Soldier Ani.cpp b/Tactical/Soldier Ani.cpp index d3c2e860..4d96c0e3 100644 --- a/Tactical/Soldier Ani.cpp +++ b/Tactical/Soldier Ani.cpp @@ -3485,6 +3485,9 @@ BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse ) case SOLDIER_CLASS_CREATURE : gMercProfiles[ MercPtrs[ ubAttacker ]->ubProfile ].records.usKillsCreatures++; break; + case SOLDIER_CLASS_ZOMBIE : + gMercProfiles[ MercPtrs[ ubAttacker ]->ubProfile ].records.usKillsZombies++; + break; 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 cd85f7f5..1b7cab38 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -110,6 +110,8 @@ #include "Dialogue Control.h" +#include "IMP Skill Trait.h" // added by Flugente + //forward declarations of common classes to eliminate includes class OBJECTTYPE; class SOLDIERTYPE; @@ -135,6 +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 ); + + // Enumerate extended directions enum { @@ -996,6 +1002,12 @@ SOLDIERTYPE& SOLDIERTYPE::operator=(const OLDSOLDIERTYPE_101& src) this->wornSnowCamo = src.wornSnowCamo; this->bScopeMode = USE_BEST_SCOPE; + + this->bPoisonBleeding = 0; + this->bPoisonLife = 0; + this->bPoisonSum = 0; + this->bPoisonResistance = 0; + this->bPoisonAbsorption = 0; } return *this; } @@ -1337,6 +1349,7 @@ MERCPROFILESTRUCT& MERCPROFILESTRUCT::operator=(const OLD_MERCPROFILESTRUCT_101& this->records.usKillsAdmins = (src.usKills/4); this->records.usKillsHostiles = 0; this->records.usKillsCreatures = 0; + this->records.usKillsZombies = 0; this->records.usKillsTanks = 0; this->records.usKillsOthers = 0; this->records.usAssistsMercs = (src.usAssists*3/4); @@ -1476,7 +1489,7 @@ MERCPROFILESTRUCT& MERCPROFILESTRUCT::operator=(const OLD_MERCPROFILESTRUCT_101& this->iMercMercContractLength = src.iMercMercContractLength;//Used for MERC mercs, specifies how many days the merc has gone since last page - this->uiTotalCostToDate = src.uiTotalCostToDate;// The total amount of money that has been paid to the merc for their salary + this->uiTotalCostToDate = src.uiTotalCostToDate;// The total amount of money that has been paid to the merc for their salary } return *this; } @@ -1669,7 +1682,7 @@ void HandleSystemNewAISituation( SOLDIERTYPE *pSoldier, BOOLEAN fResetABC ); UINT16 *CreateEnemyGlow16BPPPalette( SGPPaletteEntry *pPalette, UINT32 rscale, UINT32 gscale, BOOLEAN fAdjustGreen ); UINT16 *CreateEnemyGreyGlow16BPPPalette( SGPPaletteEntry *pPalette, UINT32 rscale, UINT32 gscale, BOOLEAN fAdjustGreen ); -void SoldierBleed( SOLDIERTYPE *pSoldier, BOOLEAN fBandagedBleed ); +void SoldierBleed( SOLDIERTYPE *pSoldier, BOOLEAN fBandagedBleed, BOOLEAN fAllowPoisoning ); INT32 CheckBleeding( SOLDIERTYPE *pSoldier ); void EVENT_InternalSetSoldierDesiredDirection( SOLDIERTYPE *pSoldier, UINT8 ubNewDirection, BOOLEAN fInitalMove, UINT16 usAnimState ); @@ -5409,7 +5422,7 @@ void SOLDIERTYPE::EVENT_SoldierGotHit( UINT16 usWeaponIndex, INT16 sDamage, INT1 HandleMoraleEvent( this, MORALE_TOOK_LOTS_OF_DAMAGE, this->sSectorX, this->sSectorY, this->bSectorZ ); } } - + // SWITCH IN TYPE OF WEAPON if ( ubSpecial == FIRE_WEAPON_TOSSED_OBJECT_SPECIAL ) { @@ -5652,12 +5665,14 @@ void SOLDIERTYPE::EVENT_SoldierGotHit( UINT16 usWeaponIndex, INT16 sDamage, INT1 // OK, If we are a vehicle.... damage vehicle...( people inside... ) if ( this->flags.uiStatusFlags & SOLDIER_VEHICLE ) { - this->SoldierTakeDamage( ANIM_CROUCH, sDamage, sBreathLoss, ubReason, this->ubAttackerID, NOWHERE, FALSE, TRUE ); + this->SoldierTakeDamage( ANIM_CROUCH, sDamage, 0, sBreathLoss, ubReason, this->ubAttackerID, NOWHERE, FALSE, TRUE ); return; } + INT16 poisondamage = (INT16) ( sDamage * MercPtrs[ubAttackerID]->GetPoisonDamagePercentage() / 100 ); + // DEDUCT LIFE - ubCombinedLoss = this->SoldierTakeDamage( ANIM_CROUCH, sDamage, sBreathLoss, ubReason, this->ubAttackerID, NOWHERE, FALSE, TRUE ); + ubCombinedLoss = this->SoldierTakeDamage( ANIM_CROUCH, sDamage, poisondamage, sBreathLoss, ubReason, this->ubAttackerID, NOWHERE, FALSE, TRUE ); // ATE: OK, Let's check our ASSIGNMENT state, // If anything other than on a squad or guard, make them guard.... @@ -7053,10 +7068,6 @@ void SOLDIERTYPE::EVENT_BeginMercTurn( BOOLEAN fFromRealTime, INT32 iRealTimeCou { HandleEndTurnDrugAdjustments( this ); } - else - { - HandleEndTurnDrugAdjustments( this ); - } // ATE: Don't bleed if in AUTO BANDAGE! if ( !gTacticalStatus.fAutoBandageMode ) @@ -8657,7 +8668,16 @@ void SOLDIERTYPE::BeginSoldierClimbUpRoof( void ) this->ubPendingDirection = bNewDirection; //this->usPendingAnimation = CLIMBUPROOF; - this->EVENT_InitNewSoldierAnim( CLIMBUPROOF, 0 , FALSE ); + + // Flugente: In case an animation is missing (zombies with bodytype of civilians), we TELEPORT instead + if ( IsAnimationValidForBodyType( this, CLIMBUPROOF ) == FALSE ) + { + SetSoldierHeight( 50.0 ); + TeleportSoldier( this, this->sTempNewGridNo, TRUE ); + EndAIGuysTurn( this); + } + else + this->EVENT_InitNewSoldierAnim( CLIMBUPROOF, 0 , FALSE ); this->InternalReceivingSoldierCancelServices( FALSE ); this->InternalGivingSoldierCancelServices( FALSE ); @@ -8810,7 +8830,7 @@ void SOLDIERTYPE::BeginSoldierClimbWallUp( void ) UINT32 SleepDartSuccumbChance( SOLDIERTYPE * pSoldier ) { UINT32 uiChance; - INT8 bEffectiveStrength; + INT16 bEffectiveStrength; // figure out base chance of succumbing, bEffectiveStrength = EffectiveStrength( pSoldier ); @@ -9006,12 +9026,12 @@ void HandleTakeDamageDeath( SOLDIERTYPE *pSoldier, UINT8 bOldLife, UINT8 ubReaso } -UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBreathLoss, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage ) +UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sPoisonAdd, INT16 sBreathLoss, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage ) { #ifdef JA2BETAVERSION if (is_networked) { CHAR tmpMPDbgString[512]; - sprintf(tmpMPDbgString,"SoldierTakeDamage ( bHeight : %i , sLifeDeduct : %i , sBreathLoss : %i , ubReason : %i , ubAttacker : %i , sSourceGrid : %i , sSubsequent : %i , fShowDamage : %i )\n", bHeight , sLifeDeduct , sBreathLoss , ubReason , ubAttacker , sSourceGrid , sSubsequent , fShowDamage ); + sprintf(tmpMPDbgString,"SoldierTakeDamage ( bHeight : %i , sLifeDeduct : %i , sPoisonAdd : %i , sBreathLoss : %i , ubReason : %i , ubAttacker : %i , sSourceGrid : %i , sSubsequent : %i , fShowDamage : %i )\n", bHeight , sLifeDeduct , sPoisonAdd, sBreathLoss , ubReason , ubAttacker , sSourceGrid , sSubsequent , fShowDamage ); MPDebugMsg(tmpMPDbgString); } #endif @@ -9025,6 +9045,11 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr this->ubLastDamageReason = ubReason; + // check: poison damage cannot be higher than damage + sPoisonAdd = min(sPoisonAdd, sLifeDeduct); + + // reduce poison damage by poison resistance + sPoisonAdd = (INT16)(sPoisonAdd * (100 - this->GetPoisonResistance())/100); // CJC Jan 21 99: add check to see if we are hurting an enemy in an enemy-controlled // sector; if so, this is a sign of player activity @@ -9059,6 +9084,11 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr // Deduct life!, Show damage if we want! bOldLife = this->stats.bLife; + // we need this to relation to decide how to redistribute poison points + FLOAT dpoisonliferelation = 1.0; + if ( bOldLife > 0 ) + dpoisonliferelation = (FLOAT)(this->bPoisonLife / bOldLife); + // OK, If we are a vehicle.... damage vehicle...( people inside... ) if ( this->flags.uiStatusFlags & SOLDIER_VEHICLE ) { @@ -9182,11 +9212,41 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr if (sLifeDeduct > this->stats.bLife) { this->stats.bLife = 0; + + this->bPoisonSum = 0; + this->bPoisonLife = 0; + this->bPoisonBleeding = 0; } else { + // we might have poison absorption + INT16 sLifeGainAbsorption = (INT16) ((sPoisonAdd * this->GetPoisonAbsorption())/100); + + // life reduction gets reduced itself by life gained through absorption + sLifeDeduct -= sLifeGainAbsorption; + + // poison points however get increased by absorped life points (poison was absorbed, not resisted) + INT16 poisongained = sPoisonAdd + sLifeGainAbsorption; + // Decrease Health this->stats.bLife -= sLifeDeduct; + + // life may increase by poison absorption, so make sure it doesn't rise too much + this->stats.bLife = min(this->stats.bLife, this->stats.bLifeMax); + + // increase poisoning + this->bPoisonSum = min(this->stats.bLifeMax, this->bPoisonSum + poisongained); + + // increase poison life if we gained life through absorption + if ( sLifeGainAbsorption > 0 ) + this->bPoisonLife = min(this->bPoisonSum, this->bPoisonLife + sLifeGainAbsorption); + + // if no life was lost, also add sPoisonAdd to bPoisonLife + if ( sLifeDeduct <= 0 ) + this->bPoisonLife = min(this->bPoisonSum, this->bPoisonLife + sPoisonAdd); + else + // we reduce bPoisonLife according to old percentage of poison life to life + this->bPoisonLife -= (INT8) (dpoisonliferelation * sLifeDeduct); } ///////////////////////////////////////////////////////////////////////////////////////////////// @@ -9211,7 +9271,8 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr // ATE: Put some logic in here to allow enemies to die quicker..... // Are we an enemy? - if ( this->bSide != gbPlayerNum && !this->aiData.bNeutral && this->ubProfile == NO_PROFILE ) + // zombies don't die suddenly, as they regenerate health by bloodloss and poison. You have to make sure they die! + if ( this->bSide != gbPlayerNum && !this->aiData.bNeutral && this->ubProfile == NO_PROFILE && !this->IsZombie() ) { // ATE: Give them a chance to fall down... if ( this->stats.bLife > 0 && this->stats.bLife < ( OKLIFE - 1 ) ) @@ -9219,11 +9280,15 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr // Are we taking damage from bleeding? if ( ubReason == TAKE_DAMAGE_BLOODLOSS ) { - // Fifty-fifty chance to die now! + // Fifty-fifty chance to die now! if ( Random( 3 ) == 0 || gTacticalStatus.Team[ this->bTeam ].bMenInSector == 1 ) { // Kill! this->stats.bLife = 0; + + this->bPoisonSum = 0; + this->bPoisonLife = 0; + this->bPoisonBleeding = 0; } } else @@ -9233,10 +9298,20 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr { // Kill! this->stats.bLife = 0; + + this->bPoisonSum = 0; + this->bPoisonLife = 0; + this->bPoisonBleeding = 0; } } } } + 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; + } if ( fShowDamage ) { @@ -9247,6 +9322,10 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr if ( this->stats.bLife < 0 ) { this->stats.bLife = 0; + + this->bPoisonSum = 0; + this->bPoisonLife = 0; + this->bPoisonBleeding = 0; } @@ -9267,11 +9346,29 @@ UINT8 SOLDIERTYPE::SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBr { // HTH does 1 pt bleeding per hit this->bBleeding = this->bBleeding + 1; + + // add poison point according to poison/life relation + this->bPoisonBleeding += (INT8) (dpoisonliferelation ); } } else { - this->bBleeding = this->stats.bLifeMax - ( this->stats.bLife + bBandage ); + // if we would actually bleed _less_ after applying the damage received (poison absorption), we reduce bleeding only if the new bBandage would be zero + // by this, we can continue bleeding, and eventually bleeding + // the normal behaviour is unchanged, as this case can't happen without poison absorption + if ( sLifeDeduct < 0 ) + { + INT8 oldBleeding = this->bBleeding; + this->bBleeding = min(this->bBleeding, this->stats.bLifeMax - this->stats.bLife ); + this->bPoisonBleeding = max(0, this->bPoisonBleeding - (oldBleeding - this->bBleeding)); + } + else + { + this->bBleeding = this->stats.bLifeMax - ( this->stats.bLife + bBandage ); + + // we increase bPoisonLife according to old percentage of poison life to life + this->bPoisonBleeding += (INT8) (dpoisonliferelation * sLifeDeduct); + } } } @@ -10089,7 +10186,7 @@ BOOLEAN SOLDIERTYPE::CheckSoldierHitRoof( void ) //this->EVENT_InitNewSoldierAnim( FALLFORWARD_ROOF, 0 , FALSE ); // Deduct hitpoints/breath for falling! - this->SoldierTakeDamage( ANIM_CROUCH, 100, 5000, TAKE_DAMAGE_FALLROOF, NOBODY, NOWHERE, 0, TRUE ); + this->SoldierTakeDamage( ANIM_CROUCH, 100, 0, 5000, TAKE_DAMAGE_FALLROOF, NOBODY, NOWHERE, 0, TRUE ); fReturnVal = TRUE; @@ -10104,7 +10201,7 @@ BOOLEAN SOLDIERTYPE::CheckSoldierHitRoof( void ) this->usPendingAnimation = FALLOFF; // Deduct hitpoints/breath for falling! - this->SoldierTakeDamage( ANIM_CROUCH, 100, 5000, TAKE_DAMAGE_FALLROOF, NOBODY, NOWHERE, 0, TRUE ); + this->SoldierTakeDamage( ANIM_CROUCH, 100, 0, 5000, TAKE_DAMAGE_FALLROOF, NOBODY, NOWHERE, 0, TRUE ); fReturnVal = TRUE; } @@ -10139,15 +10236,21 @@ void SOLDIERTYPE::BeginSoldierClimbDownRoof( void ) SetUIBusy( this->ubID ); } - - - this->sTempNewGridNo = NewGridNo( this->sGridNo, (UINT16)DirectionInc(bNewDirection ) ); bNewDirection = gTwoCDirection[ bNewDirection ]; this->ubPendingDirection = bNewDirection; - this->EVENT_InitNewSoldierAnim( JUMPDOWNWALL, 0 , FALSE ); + + // Flugente: In case an animation is missing (zombies with bodytype of civilians), we TELEPORT instead + if ( IsAnimationValidForBodyType( this, JUMPDOWNWALL ) == FALSE ) + { + SetSoldierHeight( 0.0 ); + TeleportSoldier( this, this->sTempNewGridNo, TRUE ); + EndAIGuysTurn( this); + } + else + this->EVENT_InitNewSoldierAnim( JUMPDOWNWALL, 0 , FALSE ); this->InternalReceivingSoldierCancelServices( FALSE ); this->InternalGivingSoldierCancelServices( FALSE ); @@ -10952,6 +11055,9 @@ void SOLDIERTYPE::ReviveSoldier( void ) this->stats.bLife = this->stats.bLifeMax; this->bBleeding = 0; + this->bPoisonBleeding = 0; + this->bPoisonLife = 0; + this->bPoisonSum = 0; this->iHealableInjury = 0; // added by SANDRO this->ubDesiredHeight = ANIM_STAND; @@ -11376,7 +11482,7 @@ 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 ) // SANDRO - added check for body type + if ( fMartialArtist && !AreInMeanwhile( ) && !Item[usItem].crowbar && this->ubBodyType == REGMALE && !IsZombie() ) // SANDRO - added check for body type #endif { // Are we in attack mode yet? @@ -11391,66 +11497,136 @@ void SOLDIERTYPE::EVENT_SoldierBeginPunchAttack( INT32 sGridNo, UINT8 ubDirectio } else { - // Look at stance of target - switch( gAnimControl[ pTSoldier->usAnimState ].ubEndHeight ) + // 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 ) { - case ANIM_STAND: - case ANIM_CROUCH: - - if ( !Item[usItem].crowbar ) + if ( gGameExternalOptions.fZombieExplodingCivs ) { - this->EVENT_InitNewSoldierAnim( PUNCH, 0 , FALSE ); + // Solution 1: zombie explodes, killing himself and eventually also killing his target + // Lower hitpoints, to ensure we will die in explosion (otherwise there might be multiple explosions or zombies not dying correctly...) + if ( stats.bLife > 2 ) + stats.bLife = 2; + // Explosion of a Jar of RDX Crystals + IgniteExplosion( this->ubID, this->sX, this->sY, (INT16) (gpWorldLevelData[this->sGridNo].sHeight), this->sGridNo, 136, this->pathing.bLevel ); } else { - this->EVENT_InitNewSoldierAnim( CROWBAR_ATTACK, 0 , FALSE ); - } - - // CHECK IF HE CAN SEE US, IF SO CHANGE DIR - if ( pTSoldier->aiData.bOppList[ this->ubID ] == 0 && pTSoldier->bTeam != this->bTeam ) - { - // Get direction to target - // IF WE ARE AN ANIMAL, CAR, MONSTER, DONT'T TURN - if ( !( pTSoldier->flags.uiStatusFlags & ( SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE ) ) ) + // Solution 2: Crude simulation of a 'melee' attack (this is a rig-up, a better solution needs to be found). + // do we 'hit'? + if ( Random(100) > 30 ) { - // OK, stop merc.... - pTSoldier->EVENT_StopMerc( pTSoldier->sGridNo, pTSoldier->ubDirection ); + // Play sound + PlayJA2SampleFromFile( "Sounds\\zombie_swish1.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN ); - if ( pTSoldier->bTeam != gbPlayerNum ) + INT8 oldlife = pTSoldier->stats.bLife; + + INT16 damage = (INT16)(5 + Random(20)); + if ( pTSoldier->stats.bLife - damage < 0 ) + damage = oldlife; + + // We've got a problem if we kill someone outright ithout him collapsing properly... + // FIX: We'll adjust our damage, so if we'd kill someone without collapsing first, we lower our damage, to let him collapse + // After all, this whole thing's a rig up, so there shouldn't be a problem with that + if ( oldlife >= OKLIFE && oldlife <= damage ) + damage -= (INT16)((5 + Random(5))); + + INT16 breathdamage = (INT16)(500 + Random(1500)); + if ( pTSoldier->bBreath - breathdamage < 0 ) + breathdamage = pTSoldier->bBreath; + + INT16 poisondamage = (INT16) ( sDamage * this->GetPoisonDamagePercentage() / 100 ); + + // zombies do ~50% poison damage + pTSoldier->SoldierTakeDamage( 0, damage, poisondamage, breathdamage, TAKE_DAMAGE_HANDTOHAND, this->ubID, pTSoldier->sGridNo, 0, TRUE ); + + if ( pTSoldier->stats.bLife == 0 ) { - CancelAIAction( pTSoldier, TRUE ); + // FINISH HIM! + HandleTakeDamageDeath( pTSoldier, oldlife, TAKE_DAMAGE_BLOODLOSS ); + } + else if ( pTSoldier->stats.bLife < OKLIFE && !pTSoldier->bCollapsed ) + { + // let the target collapse... + SoldierCollapse(pTSoldier); } - - ubTDirection = (UINT8)GetDirectionFromGridNo( this->sGridNo, pTSoldier ); - SendSoldierSetDesiredDirectionEvent( pTSoldier, ubTDirection ); - } - } - break; - - case ANIM_PRONE: - - // CHECK OUR STANCE - // ATE: Added this for CIV body types 'cause of elliot - if ( !IS_MERC_BODY_TYPE( this ) ) - { - this->EVENT_InitNewSoldierAnim( PUNCH, 0 , FALSE ); - } - else - { - if ( gAnimControl[ this->usAnimState ].ubEndHeight != ANIM_CROUCH ) - { - // SET DESIRED STANCE AND SET PENDING ANIMATION - SendChangeSoldierStanceEvent( this, ANIM_CROUCH ); - this->usPendingAnimation = PUNCH_LOW; } else { - // USE crouched one - // NEED TO CHANGE STANCE IF NOT CROUCHD! - this->EVENT_InitNewSoldierAnim( PUNCH_LOW, 0 , FALSE ); + // Play sound + PlayJA2SampleFromFile( "Sounds\\zombie_swish2.wav", RATE_11025, HIGHVOLUME, 1, MIDDLEPAN ); } + + // zombie is done doing harm... + EndAIGuysTurn( this); + } + + // ensure that we are running on our way to the next enemy + this->EVENT_InitNewSoldierAnim( RUNNING, 0 , FALSE ); + } + else + { + // Look at stance of target + switch( gAnimControl[ pTSoldier->usAnimState ].ubEndHeight ) + { + case ANIM_STAND: + case ANIM_CROUCH: + + if ( !Item[usItem].crowbar ) + { + this->EVENT_InitNewSoldierAnim( PUNCH, 0 , FALSE ); + } + else + { + this->EVENT_InitNewSoldierAnim( CROWBAR_ATTACK, 0 , FALSE ); + } + + // CHECK IF HE CAN SEE US, IF SO CHANGE DIR + if ( pTSoldier->aiData.bOppList[ this->ubID ] == 0 && pTSoldier->bTeam != this->bTeam ) + { + // Get direction to target + // IF WE ARE AN ANIMAL, CAR, MONSTER, DONT'T TURN + if ( !( pTSoldier->flags.uiStatusFlags & ( SOLDIER_MONSTER | SOLDIER_ANIMAL | SOLDIER_VEHICLE ) ) ) + { + // OK, stop merc.... + pTSoldier->EVENT_StopMerc( pTSoldier->sGridNo, pTSoldier->ubDirection ); + + if ( pTSoldier->bTeam != gbPlayerNum ) + { + CancelAIAction( pTSoldier, TRUE ); + } + + ubTDirection = (UINT8)GetDirectionFromGridNo( this->sGridNo, pTSoldier ); + SendSoldierSetDesiredDirectionEvent( pTSoldier, ubTDirection ); + } + } + break; + + case ANIM_PRONE: + + // CHECK OUR STANCE + // ATE: Added this for CIV body types 'cause of elliot + if ( !IS_MERC_BODY_TYPE( this ) ) + { + this->EVENT_InitNewSoldierAnim( PUNCH, 0 , FALSE ); + } + else + { + if ( gAnimControl[ this->usAnimState ].ubEndHeight != ANIM_CROUCH ) + { + // SET DESIRED STANCE AND SET PENDING ANIMATION + SendChangeSoldierStanceEvent( this, ANIM_CROUCH ); + this->usPendingAnimation = PUNCH_LOW; + } + else + { + // USE crouched one + // NEED TO CHANGE STANCE IF NOT CROUCHD! + this->EVENT_InitNewSoldierAnim( PUNCH_LOW, 0 , FALSE ); + } + } + break; } - break; } } @@ -11682,7 +11858,7 @@ void SOLDIERTYPE::EVENT_SoldierEnterVehicle( INT32 sGridNo, UINT8 ubDirection ) UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT16 sStatus ) { UINT32 uiDressSkill, uiPossible, uiActual, uiMedcost, uiDeficiency, uiAvailAPs, uiUsedAPs; - UINT8 ubBelowOKlife, ubPtsLeft = 0; + UINT8 ubBelowOKlife = 0, ubPtsLeft = 0; BOOLEAN fRanOut = FALSE; BOOLEAN fOnSurgery = FALSE; INT8 bInitialBleeding; @@ -11860,6 +12036,10 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1 // use up appropriate # of actual healing points ubPtsLeft -= (2 * ubBelowOKlife); + + // raise bPoisonLife, reduce bPoisonBleeding + pVictim->bPoisonLife = min(pVictim->bPoisonLife + ubBelowOKlife, pVictim->bPoisonSum); + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - ubBelowOKlife); } else { @@ -11875,12 +12055,17 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1 pVictim->bBleeding -= ( ubPtsLeft / 2); ubPtsLeft = ubPtsLeft % 2; // if ptsLeft was odd, ptsLeft = 1 + + // raise bPoisonLife, reduce bPoisonBleeding + pVictim->bPoisonLife = min(pVictim->bPoisonLife + ( ubPtsLeft / 2), pVictim->bPoisonSum); + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - ( ubPtsLeft / 2)); } // this should never happen any more, but make sure bleeding not negative if (pVictim->bBleeding < 0) { pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; } // if this healing brought the patient out of the worst of it, cancel dying @@ -11970,11 +12155,13 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1 if (pVictim->bBleeding >= (usLifeReturned/100)) { pVictim->bBleeding -= (usLifeReturned/100); + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - (usLifeReturned/100) ); uiMedcost += (usLifeReturned / 200); // add medkit points cost for unbandaged part } else { pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; uiMedcost += max( 0, (((usLifeReturned/100) - pVictim->bBleeding) / 2)); // add medkit points cost for unbandaged part } } @@ -11983,6 +12170,7 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1 pVictim->stats.bLife = pVictim->stats.bLifeMax; pVictim->iHealableInjury = 0; pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; } // Reduce max breath based on life returned if ( (pVictim->bBreathMax - (((usLifeReturned/100) * gSkillTraitValues.usDOSurgeryMaxBreathLoss )/ 100)) <= BREATHMAX_ABSOLUTE_MINIMUM ) @@ -12010,10 +12198,12 @@ UINT32 SOLDIERTYPE::SoldierDressWound( SOLDIERTYPE *pVictim, INT16 sKitPts, INT1 { ubPtsLeft -= pVictim->bBleeding; pVictim->bBleeding = 0; + pVictim->bPoisonBleeding = 0; } else // bandage what we can { pVictim->bBleeding -= ubPtsLeft; + pVictim->bPoisonBleeding = max(0, pVictim->bPoisonBleeding - ubPtsLeft); ubPtsLeft = 0; } @@ -13189,6 +13379,8 @@ INT32 SOLDIERTYPE::GetDamageResistance(BOOLEAN fAutoResolve, BOOLEAN fCalcBreath resistance += gGameExternalOptions.sEnemyRegularDamageResistance; else if (this->ubSoldierClass == SOLDIER_CLASS_ELITE && gGameExternalOptions.sEnemyEliteDamageResistance != 0) resistance += gGameExternalOptions.sEnemyEliteDamageResistance; + else if (IsZombie()) + resistance += gGameExternalOptions.sEnemyZombieDamageResistance; } ////////////////////////////////////////////////////////////////////////////////////// @@ -13236,6 +13428,74 @@ INT32 SOLDIERTYPE::GetDamageResistance(BOOLEAN fAutoResolve, BOOLEAN fCalcBreath return( resistance ); } +INT16 SOLDIERTYPE::GetSoldierCriticalDamageBonus( void ) +{ + INT16 val = 0; + + return val; +} + +BOOLEAN SOLDIERTYPE::IsZombie( void ) +{ + return( ubSoldierClass == SOLDIER_CLASS_ZOMBIE ); +} + +INT16 SOLDIERTYPE::GetPoisonResistance( void ) +{ + // Flugente: resistance can per definition only be between -100 and 100 (at least that's my definition) + INT16 val = bPoisonResistance; + + // without WH40K, there are no modifiers + + val = max(-100, val); + val = min(100, val); + + return( val ); +} + +INT16 SOLDIERTYPE::GetPoisonAbsorption( void ) +{ + // Flugente: absorption can per definition only be >= 0 (at least that's my definition) + INT16 val = bPoisonAbsorption; + + val = max(0, val); + + return( val ); +} + + +INT16 SOLDIERTYPE::GetPoisonDamagePercentage( void ) +{ + // Flugente: this percentage has to be between 0% and 100% + INT16 val = 0; + + // zombies poison damage percentage is externalised + if ( IsZombie() ) + val += gGameExternalOptions.sZombiePoisonDamagePercentage; + + if ( this->usAttackingWeapon ) + { + val += Item[this->usAttackingWeapon].bPoisonPercentage; + + if ( Item[this->usAttackingWeapon].usItemClass == IC_GUN ) + { + // check for modificators (attachments, poisoned ammunition) + // check for poisoned ammunition + UINT8 ammotype = this->inv[ this->ubAttackingHand ][0]->data.gun.ubGunAmmoType; + val += AmmoTypes[ammotype].poisonPercentage; + + // check for attachments with a bonus to poison (currently, none exist) + } + } + + val = max(0, val); + val = min(100, val); + + return( val ); +} + + + INT32 CheckBleeding( SOLDIERTYPE *pSoldier ) { INT8 bBandaged; //,savedOurTurn; @@ -13244,94 +13504,116 @@ INT32 CheckBleeding( SOLDIERTYPE *pSoldier ) if ( pSoldier->stats.bLife != 0 ) { // if merc is hurt beyond the minimum required to bleed, or he's dying - if ( ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD) || pSoldier->stats.bLife < OKLIFE ) + // Flugente: also allow bleeding if we would regenerate health through it, which happens when we have enough absorption and are poisoned enough (only guaranteed if fully poisoned) + if ( ( ( pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD) || pSoldier->stats.bLife < OKLIFE ) || ( pSoldier->bPoisonSum == pSoldier->stats.bLifeMax && pSoldier->GetPoisonAbsorption() >= 200 ) ) { // if he's NOT in the process of being bandaged or DOCTORed if ( ( pSoldier->ubServiceCount == 0 ) && ( AnyDoctorWhoCanHealThisPatient( pSoldier, HEALABLE_EVER ) == NULL ) ) { // may drop blood whether or not any bleeding takes place this turn if ( pSoldier->bTilesMoved < 1 ) - { - iBlood = ( ( pSoldier->bBleeding - MIN_BLEEDING_THRESHOLD ) / BLOODDIVISOR ); // + pSoldier->dying; - if ( iBlood > MAXBLOODQUANTITY ) - { - iBlood = MAXBLOODQUANTITY; - } - } + { + iBlood = ( ( pSoldier->bBleeding - MIN_BLEEDING_THRESHOLD ) / BLOODDIVISOR ); // + pSoldier->dying; + if ( iBlood > MAXBLOODQUANTITY ) + { + iBlood = MAXBLOODQUANTITY; + } + } else - { - iBlood = NOBLOOD; - } + { + iBlood = NOBLOOD; + } // Are we in a different mode? if ( !(gTacticalStatus.uiFlags & TURNBASED ) || !(gTacticalStatus.uiFlags & INCOMBAT ) ) - { - pSoldier->dNextBleed -= (FLOAT)RT_NEXT_BLEED_MODIFIER; - } + { + pSoldier->dNextBleed -= (FLOAT)RT_NEXT_BLEED_MODIFIER; + } else - { - // Do a single step descrease - pSoldier->dNextBleed--; - } + { + // Do a single step descrease + pSoldier->dNextBleed--; + } // if it's time to lose some blood if ( pSoldier->dNextBleed <= 0) - { - // first, calculate if soldier is bandaged - bBandaged = pSoldier->stats.bLifeMax - pSoldier->bBleeding - pSoldier->stats.bLife; + { + // first, calculate if soldier is bandaged + bBandaged = pSoldier->stats.bLifeMax - pSoldier->bBleeding - pSoldier->stats.bLife; - // as long as he's bandaged and not "dying" - if ( bBandaged && pSoldier->stats.bLife >= OKLIFE ) - { - // just bleeding through existing bandages - pSoldier->bBleeding++; - - SoldierBleed( pSoldier, TRUE ); - } - else // soldier is either not bandaged at all or is dying - { - if ( pSoldier->stats.bLife < OKLIFE ) // if he's dying + // as long as he's bandaged and not "dying" + if ( bBandaged && pSoldier->stats.bLife >= OKLIFE ) { - // if he's conscious, and he hasn't already, say his "dying quote" - if ( ( pSoldier->stats.bLife >= CONSCIOUSNESS ) && !pSoldier->flags.fDyingComment ) + // just bleeding through existing bandages + pSoldier->bBleeding++; + + SoldierBleed( pSoldier, TRUE, TRUE ); + } + else // soldier is either not bandaged at all or is dying + { + if ( pSoldier->stats.bLife < OKLIFE ) // if he's dying { - TacticalCharacterDialogue( pSoldier, QUOTE_SERIOUSLY_WOUNDED ); + // if he's conscious, and he hasn't already, say his "dying quote" + if ( ( pSoldier->stats.bLife >= CONSCIOUSNESS ) && !pSoldier->flags.fDyingComment ) + { + TacticalCharacterDialogue( pSoldier, QUOTE_SERIOUSLY_WOUNDED ); - pSoldier->flags.fDyingComment = TRUE; - } + pSoldier->flags.fDyingComment = TRUE; + } - // can't permit lifemax to ever bleed beneath OKLIFE, or that - // soldier might as well be dead! - if (pSoldier->stats.bLifeMax >= OKLIFE) - { - // bleeding while "dying" costs a PERMANENT point of life each time! - pSoldier->stats.bLifeMax--; - pSoldier->bBleeding--; - if ( pSoldier->iHealableInjury >= 100) // added check for insta-healable injury - SANDRO - pSoldier->iHealableInjury -= 100; - } - } - } + // can't permit lifemax to ever bleed beneath OKLIFE, or that + // soldier might as well be dead! + if (pSoldier->stats.bLifeMax >= OKLIFE) + { + // Flugente: reduce PERMANENT points of life only if through 'normal' bleeding, not by poisoning + // problem is that this function applies every bleeding cycle, while loosing points through natural restoration (too much poison in body) only happens every hour. + // so one might lose 1pt of life through poisoning at 8:00, and then lose 30 points of life PERMANTENLY in the following hour without dying + // We bypass this by only allowing PERMANTENT lifeloss if really bleeding + if ( pSoldier->bBleeding ) + { + // bleeding while "dying" costs a PERMANENT point of life each time! + pSoldier->stats.bLifeMax--; + pSoldier->bBleeding = max(0, pSoldier->bBleeding - 1); - // either way, a point of life (health) is lost because of bleeding - // This will also update the life bar + // the lost point might be poisoned + pSoldier->bPoisonSum = max(0, pSoldier->bPoisonSum - 1); - SoldierBleed( pSoldier, FALSE ); + // also adjust poisoned bleeding + pSoldier->bPoisonBleeding = max(0, pSoldier->bPoisonBleeding - 1); + + if ( pSoldier->iHealableInjury >= 100) // added check for insta-healable injury - SANDRO + pSoldier->iHealableInjury -= 100; + } + } + } + } + + // either way, a point of life (health) is lost because of bleeding + // Flugente: not true anymore! We might be fully bandaged, not bleeding, but still receive poison damage that will lower our life. So check if we're bleeding! + if ( pSoldier->bBleeding ) + { + // This will also update the life bar + SoldierBleed( pSoldier, FALSE, TRUE ); + } + else + { + // just to update everything, like going unconscious or dying + pSoldier->SoldierTakeDamage( ANIM_CROUCH, 0, 0, 0, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); + } - // if he's not dying (which includes him saying the dying quote just - // now), and he hasn't warned us that he's bleeding yet, he does so - // Also, not if they are being bandaged.... - if ( ( pSoldier->stats.bLife >= OKLIFE ) && !pSoldier->flags.fDyingComment && !pSoldier->flags.fWarnedAboutBleeding && !gTacticalStatus.fAutoBandageMode && pSoldier->ubServiceCount == 0 ) - { + // if he's not dying (which includes him saying the dying quote just + // now), and he hasn't warned us that he's bleeding yet, he does so + // Also, not if they are being bandaged.... + if ( ( pSoldier->stats.bLife >= OKLIFE ) && !pSoldier->flags.fDyingComment && !pSoldier->flags.fWarnedAboutBleeding && !gTacticalStatus.fAutoBandageMode && pSoldier->ubServiceCount == 0 ) + { TacticalCharacterDialogue( pSoldier, QUOTE_STARTING_TO_BLEED ); // "starting to bleed" quote pSoldier->flags.fWarnedAboutBleeding = TRUE; - } - - pSoldier->dNextBleed = CalcSoldierNextBleed( pSoldier ); + } + pSoldier->dNextBleed = CalcSoldierNextBleed( pSoldier ); } } } @@ -13340,10 +13622,8 @@ INT32 CheckBleeding( SOLDIERTYPE *pSoldier ) } -void SoldierBleed( SOLDIERTYPE *pSoldier, BOOLEAN fBandagedBleed ) +void SoldierBleed( SOLDIERTYPE *pSoldier, BOOLEAN fBandagedBleed, BOOLEAN fAllowPoisoning ) { - INT8 bOldLife; - // OK, here make some stuff happen for bleeding // A banaged bleed does not show damage taken , just through existing bandages @@ -13360,21 +13640,30 @@ void SoldierBleed( SOLDIERTYPE *pSoldier, BOOLEAN fBandagedBleed ) SetInfoChar( pSoldier->ubID ); } } + + // if poisoning is allowed, if we are poisoned and we can be sure that we are really bleeding (safety reasons), there is a chance that the poisoning spreads + // this simply means that the new Bleeding point will also be a poisoned bleeding point + INT16 poisondamage = 0; + if ( fAllowPoisoning && pSoldier->bPoisonSum > 0 && pSoldier->bBleeding ) + { + INT8 bleedingpercentagepoison = (INT8) (100 * pSoldier->bPoisonSum / pSoldier->stats.bLifeMax); - bOldLife = pSoldier->stats.bLife; + if ( (INT8) Random(100) < bleedingpercentagepoison ) + poisondamage = 1; + } // If we are already dead, don't show damage! if ( !fBandagedBleed ) { // SANDRO - if the soldier is bleeding out, consider this damage as done by the last attacker if ( pSoldier->ubAttackerID != NOBODY ) - pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, 100, TAKE_DAMAGE_BLOODLOSS, pSoldier->ubAttackerID, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, poisondamage, 100, TAKE_DAMAGE_BLOODLOSS, pSoldier->ubAttackerID, NOWHERE, 0, TRUE ); else if ( pSoldier->ubPreviousAttackerID != NOBODY ) - pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, 100, TAKE_DAMAGE_BLOODLOSS, pSoldier->ubPreviousAttackerID, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, poisondamage, 100, TAKE_DAMAGE_BLOODLOSS, pSoldier->ubPreviousAttackerID, NOWHERE, 0, TRUE ); else if ( pSoldier->ubNextToPreviousAttackerID != NOBODY ) - pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, 100, TAKE_DAMAGE_BLOODLOSS, pSoldier->ubNextToPreviousAttackerID, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, poisondamage, 100, TAKE_DAMAGE_BLOODLOSS, pSoldier->ubNextToPreviousAttackerID, NOWHERE, 0, TRUE ); else - pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_CROUCH, 1, poisondamage, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); } } diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index e81b7a61..4e43d7e1 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -309,6 +309,7 @@ enum SOLDIER_CLASS_ELITE_MILITIA, SOLDIER_CLASS_CREATURE, SOLDIER_CLASS_MINER, + SOLDIER_CLASS_ZOMBIE, }; #define SOLDIER_CLASS_ENEMY( bSoldierClass ) ( ( bSoldierClass >= SOLDIER_CLASS_ADMINISTRATOR ) && ( bSoldierClass <= SOLDIER_CLASS_ARMY ) ) @@ -1143,6 +1144,21 @@ public: // I don't know if this is a good idea at all... //INT16 filler; + // Flugente: Is this the correct position? + INT8 bScopeMode; + + /////////////////////////////////////////////////////// + // Flugente Zombies: Added variables for the poison system + INT8 bPoisonBleeding; // The number of bleeding points that are also poison points + //INT8 bPoisonBandaged; // The number of bandaged lifepoints that are also poison points + INT8 bPoisonLife; // The number of Lifepoints that are also poison points + INT8 bPoisonSum; // The sum of poison points; + + INT16 bPoisonResistance; // poison resistance reduces the amount of poison damage received. It is applied before poison absorption + INT16 bPoisonAbsorption; // for x points of poison damage received, you gain x * (bPoisonAbsorption / 100) poison life points + /////////////////////////////////////////////////////// + + #ifdef JA2UB //ja25 BOOLEAN fIgnoreGetupFromCollapseCheck; @@ -1151,10 +1167,7 @@ public: UINT8 ubPercentDamageInflictedByTeam[NUM_ASSIST_SLOTS]; //The percent of damage inflicted by the player team. Each element corresponds to the Soldier ID. Each element contains the percent damage inflicted by that merc #endif - - // Flugente: Is this the correct position? - INT8 bScopeMode; - + char endOfPOD; // marker for end of POD (plain old data) // Note: Place all non-POD items at the end (after endOfPOD) @@ -1229,7 +1242,8 @@ public: void ChangeSoldierStance( UINT8 ubDesiredStance ); void StopSoldier( void ); void ReviveSoldier( void ); - UINT8 SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sBreathDeduct, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage ); + // Flugente: added poison damage, which should be smaller or equal than sLifeDeduct + UINT8 SoldierTakeDamage( INT8 bHeight, INT16 sLifeDeduct, INT16 sPoisonAdd, INT16 sBreathDeduct, UINT8 ubReason, UINT8 ubAttacker, INT32 sSourceGrid, INT16 sSubsequent, BOOLEAN fShowDamage ); // Palette functions for soldiers @@ -1330,6 +1344,19 @@ public: // returns damage resistance in percent INT32 GetDamageResistance( BOOLEAN fAutoResolve = FALSE, BOOLEAN fCalcBreathLoss = FALSE); + INT16 GetSoldierCriticalDamageBonus( void ); // Flugente: determines critical damage bonus depending on class, skill, etc. + + // Flugente: Zombies + BOOLEAN IsZombie( void ); + + // 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 + INT16 GetPoisonResistance( void ); + INT16 GetPoisonAbsorption( void ); + // returns the poison percentage of the damage we will be doing with the weapon currently in our hand + INT16 GetPoisonDamagePercentage( void ); + ////////////////////////////////////////////////////////////////////////////// + }; // SOLDIERTYPE; #define SIZEOF_SOLDIERTYPE_POD offsetof( SOLDIERTYPE, endOfPOD ) diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index cd170627..40b6cdb3 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -341,6 +341,7 @@ SOLDIERCREATE_STRUCT::SOLDIERCREATE_STRUCT() { SOLDIERCREATE_STRUCT::SOLDIERCREATE_STRUCT(const SOLDIERCREATE_STRUCT& src) { memcpy(this, &src, SIZEOF_SOLDIERCREATE_STRUCT_POD); this->Inv = src.Inv; + this->fNoGenNewPalette = src.fNoGenNewPalette; } // Assignment operator @@ -349,6 +350,7 @@ SOLDIERCREATE_STRUCT& SOLDIERCREATE_STRUCT::operator=(const SOLDIERCREATE_STRUCT if (this != &src) { memcpy(this, &src, SIZEOF_SOLDIERCREATE_STRUCT_POD); this->Inv = src.Inv; + this->fNoGenNewPalette = src.fNoGenNewPalette; } return *this; } @@ -1727,6 +1729,10 @@ BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREAT { swprintf( pSoldier->name, gzLateLocalizedString[ 36 ] ); } + else if ( pSoldier->IsZombie() ) + { + swprintf( pSoldier->name, TacticalStr[ ZOMBIE_TEAM_MERC_NAME ] ); + } else { swprintf( pSoldier->name, TacticalStr[ CREATURE_TEAM_MERC_NAME ] ); break; @@ -1735,7 +1741,8 @@ BOOLEAN TacticalCopySoldierFromCreateStruct( SOLDIERTYPE *pSoldier, SOLDIERCREAT } //Generate colors for soldier based on the body type. - GeneratePaletteForSoldier( pSoldier, pCreateStruct->ubSoldierClass ); + if ( !pCreateStruct->fNoGenNewPalette ) + GeneratePaletteForSoldier( pSoldier, pCreateStruct->ubSoldierClass ); // Copy item info over pSoldier->inv = pCreateStruct->Inv; @@ -2170,6 +2177,7 @@ void CreateDetailedPlacementGivenBasicPlacementInfo( SOLDIERCREATE_STRUCT *pp, B switch( ubSoldierClass ) { case SOLDIER_CLASS_ADMINISTRATOR: + case SOLDIER_CLASS_ZOMBIE: pp->bExpLevel = (INT8) 2 + bExpLevelModifier; break; case SOLDIER_CLASS_ARMY: @@ -2287,7 +2295,7 @@ 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")); - if( !bp->fDetailedPlacement && ubSoldierClass != SOLDIER_CLASS_NONE && ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_MINER ) + if( !bp->fDetailedPlacement && ubSoldierClass != SOLDIER_CLASS_NONE && ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_MINER && ubSoldierClass != SOLDIER_CLASS_ZOMBIE ) GenerateRandomEquipment( pp, ubSoldierClass, bp->bRelativeEquipmentLevel); DecideToAssignSniperOrders(pp); @@ -2455,7 +2463,7 @@ void CreateDetailedPlacementGivenStaticDetailedPlacementAndBasicPlacementInfo( //{ // ReplaceExtendedGuns( pp, bp->ubSoldierClass ); //} - if( bp->ubSoldierClass != SOLDIER_CLASS_NONE && bp->ubSoldierClass != SOLDIER_CLASS_CREATURE && bp->ubSoldierClass != SOLDIER_CLASS_MINER ) + if( bp->ubSoldierClass != SOLDIER_CLASS_NONE && bp->ubSoldierClass != SOLDIER_CLASS_CREATURE && bp->ubSoldierClass != SOLDIER_CLASS_MINER && bp->ubSoldierClass != SOLDIER_CLASS_ZOMBIE ) { GenerateRandomEquipment( pp, bp->ubSoldierClass, bp->bRelativeEquipmentLevel); DecideToAssignSniperOrders(pp); @@ -2650,7 +2658,7 @@ 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. - if( ubSoldierClass != SOLDIER_CLASS_CREATURE ) + if( ubSoldierClass != SOLDIER_CLASS_CREATURE && ubSoldierClass != SOLDIER_CLASS_ZOMBIE ) { //use the enemy team iStart = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; iEnd = gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; @@ -2660,7 +2668,7 @@ SOLDIERTYPE* ReserveTacticalSoldierForAutoresolve( UINT8 ubSoldierClass ) iStart = gTacticalStatus.Team[ CREATURE_TEAM ].bFirstID; iEnd = gTacticalStatus.Team[ CREATURE_TEAM ].bLastID; } - for( i = iStart; i <= iEnd; i++ ) + for( i = iStart; i <= iEnd; ++i ) { if( MercPtrs[ i ]->bActive && MercPtrs[ i ]->bInSector && MercPtrs[ i ]->stats.bLife && !TileIsOutOfBounds(MercPtrs[ i ]->sGridNo)) { @@ -2792,6 +2800,40 @@ 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 ) + { + pSoldier = ReserveTacticalSoldierForAutoresolve( SOLDIER_CLASS_ZOMBIE ); + if( 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 ); +} + SOLDIERTYPE* ReserveTacticalMilitiaSoldierForAutoresolve( UINT8 ubSoldierClass ) { INT32 i, iStart, iEnd; @@ -4195,6 +4237,8 @@ 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 + else if (ubSolClass == SOLDIER_CLASS_ZOMBIE ) + iChance = 100; // 100% chance if( foundHtH ) // if found brass knuckless, increase the chance (doesn't happen often) iChance += 35; @@ -4238,6 +4282,8 @@ 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 + else if (ubSolClass == SOLDIER_CLASS_ZOMBIE ) + iChance = 100; // 100% chance // modify the chance by preset ini setting if( gGameExternalOptions.bAssignedTraitsRarity != 0 ) diff --git a/Tactical/Soldier Create.h b/Tactical/Soldier Create.h index 87e4afeb..13332131 100644 --- a/Tactical/Soldier Create.h +++ b/Tactical/Soldier Create.h @@ -305,6 +305,9 @@ public: INT8 bUseGivenVehicleID; BOOLEAN fHasKeys; + // Flugente: do not generate new palette for soldier (for soldiers that are created from existing soldiers/corpses, like doppelgangers and zombies) + BOOLEAN fNoGenNewPalette; + // // New and OO stuff goes after here. Above this point any changes will goof up reading from files. // @@ -420,6 +423,7 @@ SOLDIERTYPE* TacticalCreateSoldier( SOLDIERCREATE_STRUCT *pCreateStruct, UINT8 * SOLDIERTYPE* TacticalCreateAdministrator(); SOLDIERTYPE* TacticalCreateEliteEnemy(); SOLDIERTYPE* TacticalCreateArmyTroop(); +SOLDIERTYPE* TacticalCreateZombie(); // Flugente Zombies SOLDIERTYPE* TacticalCreateMilitia( UINT8 ubMilitiaClass ); SOLDIERTYPE* TacticalCreateCreature( INT8 bCreatureBodyType ); diff --git a/Tactical/Soldier Profile.cpp b/Tactical/Soldier Profile.cpp index 8165f2af..f8d16787 100644 --- a/Tactical/Soldier Profile.cpp +++ b/Tactical/Soldier Profile.cpp @@ -2106,6 +2106,7 @@ SOLDIERTYPE * SwapLarrysProfiles( SOLDIERTYPE * pSoldier ) pNewProfile->records.usKillsRegulars = gMercProfiles[ ubSrcProfile ].records.usKillsRegulars; pNewProfile->records.usKillsAdmins = gMercProfiles[ ubSrcProfile ].records.usKillsAdmins; pNewProfile->records.usKillsCreatures = gMercProfiles[ ubSrcProfile ].records.usKillsCreatures; + pNewProfile->records.usKillsZombies = gMercProfiles[ ubSrcProfile ].records.usKillsZombies; pNewProfile->records.usKillsTanks = gMercProfiles[ ubSrcProfile ].records.usKillsTanks; pNewProfile->records.usKillsOthers = gMercProfiles[ ubSrcProfile ].records.usKillsOthers; pNewProfile->records.usAssistsMercs = gMercProfiles[ ubSrcProfile ].records.usAssistsMercs; diff --git a/Tactical/Tactical Turns.cpp b/Tactical/Tactical Turns.cpp index 7dd1be47..ea06a7f8 100644 --- a/Tactical/Tactical Turns.cpp +++ b/Tactical/Tactical Turns.cpp @@ -273,6 +273,11 @@ void HandleTacticalEndTurn( ) // Flugente FTW 1: Cool down all items not in a soldier's inventory CoolDownWorldItems( FALSE ); + // Flugente: raise zombies if in gamescreen and option set + if ( guiCurrentScreen == GAME_SCREEN ) + { + RaiseZombies(); + } } diff --git a/Tactical/Turn Based Input.cpp b/Tactical/Turn Based Input.cpp index dc993c40..410a697a 100644 --- a/Tactical/Turn Based Input.cpp +++ b/Tactical/Turn Based Input.cpp @@ -120,6 +120,9 @@ #include "connect.h" #include "fresh_header.h" + +#include "IMP Skill Trait.h" // added by Flugente + //forward declarations of common classes to eliminate includes class OBJECTTYPE; class SOLDIERTYPE; @@ -4316,6 +4319,7 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) // Get life back pSoldier->stats.bLife = pSoldier->stats.bLifeMax; pSoldier->bBleeding = 0; + pSoldier->bPoisonBleeding = 0; // SANDRO - erase insta-healable injury pSoldier->iHealableInjury = 0; diff --git a/Tactical/Vehicles.cpp b/Tactical/Vehicles.cpp index b55450ef..17bcbc86 100644 --- a/Tactical/Vehicles.cpp +++ b/Tactical/Vehicles.cpp @@ -1330,7 +1330,7 @@ BOOLEAN InjurePersonInVehicle( INT32 iId, SOLDIERTYPE *pSoldier, UINT8 ubPointsO } // otherwise hurt them - pSoldier->SoldierTakeDamage( 0, ubPointsOfDmg, ubPointsOfDmg, TAKE_DAMAGE_GUNFIRE, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( 0, ubPointsOfDmg, 0, ubPointsOfDmg, TAKE_DAMAGE_GUNFIRE, NOBODY, NOWHERE, 0, TRUE ); pSoldier->HandleSoldierTakeDamageFeedback( ); @@ -1359,7 +1359,7 @@ BOOLEAN KillPersonInVehicle( INT32 iId, SOLDIERTYPE *pSoldier ) } // otherwise hurt them - pSoldier->SoldierTakeDamage( 0, 100, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( 0, 100, 0, 100, TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, TRUE ); return( TRUE ); } diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 40e2173a..641fcc49 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -334,9 +334,6 @@ bool gbForceWeaponNotReady = false; bool gbForceWeaponReady = false; -// Flugente: define for maximum temperature -#define OVERHEATING_MAX_TEMPERATURE 60000.0 - enum { WEAPON_ELEMENT_NONE = 0, @@ -1328,6 +1325,22 @@ BOOLEAN OKFireWeapon( SOLDIERTYPE *pSoldier ) } +// Flugente FTW 1: Get percentage: temperature/damagethreshold +FLOAT GetGunOverheatDamagePercentage( FLOAT usTemperature, UINT16 usIndx ) +{ + FLOAT damagethreshold = Weapon[Item[ usIndx ].ubClassIndex].usOverheatingDamageThreshold; + + return usTemperature/ damagethreshold ; +} + +// Flugente FTW 1: Get percentage: temperature/jamthreshold +FLOAT GetGunOverheatJamPercentage( FLOAT usTemperature, UINT16 usIndx ) +{ + FLOAT jamthreshold = Weapon[Item[ usIndx ].ubClassIndex].usOverheatingJamThreshold; + + return usTemperature/ jamthreshold ; +} + BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ) { // ignore passed in target gridno for now @@ -5187,6 +5200,7 @@ UINT32 CalcNewChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTi // AGILITY OR EXPERIENCE FLOAT iTempPenalty = (FLOAT)__max((pTarget->stats.bExpLevel*10), pTarget->stats.bAgility); + iBaseModifier += (iTempPenalty * gGameCTHConstants.BASE_AGILE_TARGET) / 100; } @@ -6235,19 +6249,19 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, if ( TANK( pSoldier ) && iRange < MIN_TANK_RANGE ) { // 13% penalty per tile closer than min range - iChance -= 13 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE ); + iChance -= (INT32)(13 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE )); } else { // at anything other than point-blank range if (iRange > POINT_BLANK_RANGE + 10 * (AIM_PENALTY_TARGET_CROUCHED / 3) ) { - iChance -= AIM_PENALTY_TARGET_CROUCHED; + iChance -= (INT32)(AIM_PENALTY_TARGET_CROUCHED); } else if (iRange > POINT_BLANK_RANGE) { // at close range give same bonus as prone, up to maximum of AIM_PENALTY_TARGET_CROUCHED - iChance -= 3 * ((iRange - POINT_BLANK_RANGE) / CELL_X_SIZE); // penalty -3%/tile + iChance -= (INT32)(3 * ((iRange - POINT_BLANK_RANGE) / CELL_X_SIZE)); // penalty -3%/tile } } break; @@ -6255,7 +6269,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, if ( TANK( pSoldier ) && iRange < MIN_TANK_RANGE ) { // 25% penalty per tile closer than min range - iChance -= 25 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE ); + iChance -= (INT32)(25 * ( ( MIN_TANK_RANGE - iRange ) / CELL_X_SIZE ) * iPenalty); } else { @@ -6286,7 +6300,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, // e.g. 30% to aim at head at range 1, only 10% at range 3 // or 20% to aim at torso at range 1, no penalty at range 3 // NB torso aim position is 2, so (5-aimpos) is 3, for legs it's 2, for head 4 - iChance -= (5 - ubAdjAimPos - iRange / CELL_X_SIZE) * 10; + iChance -= (INT32)((5 - ubAdjAimPos - iRange / CELL_X_SIZE) * 10 * iPenalty); } break; default: @@ -8421,7 +8435,7 @@ INT32 CalcBodyImpactReduction( UINT8 ubAmmoType, UINT8 ubHitLocation ) return( iReduction ); } -INT32 ArmourProtection( SOLDIERTYPE * pTarget, UINT16 ubArmourType, INT16 * pbStatus, INT32 iImpact, UINT8 ubAmmoType, BOOLEAN *plateHit ) +INT32 ArmourProtection( SOLDIERTYPE * pTarget, UINT16 ubArmourType, INT16 * pbStatus, INT32 iImpact, UINT8 ubAmmoType, BOOLEAN *plateHit) { INT32 iProtection, iAppliedProtection, iFailure, iCoverage; @@ -8498,7 +8512,7 @@ INT32 ArmourProtection( SOLDIERTYPE * pTarget, UINT16 ubArmourType, INT16 * pbSt else if ( AmmoTypes[ubAmmoType].ignoreArmour ) { // knives and darts damage armour but are not stopped by kevlar - if (Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_VEST || Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_LEGGINGS) + if ( Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_VEST || Armour[ ubArmourType ].ubArmourClass == ARMOURCLASS_LEGGINGS ) { iProtection = 0; } @@ -8624,6 +8638,13 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, BULLET *pBullet, SOLDIERTYPE * pTarget, INT8 bStatLoss = 0; UINT8 ubAmmoType; + 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; + } + // NOTE: reduction of bullet impact due to range and obstacles is handled // in MoveBullet. @@ -8769,7 +8790,7 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, BULLET *pBullet, SOLDIERTYPE * pTarget, AdjustImpactByHitLocation( iImpact, ubHitLocation, &iImpact, &iImpactForCrits ); - + switch( ubHitLocation ) { case AIM_SHOT_HEAD: @@ -9346,7 +9367,22 @@ INT32 HTHImpact( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTarget, INT32 iHitBy, BO if (pTarget->aiData.bOppList[ pSoldier->ubID ] == NOT_HEARD_OR_SEEN && !CREATURE_OR_BLOODCAT( pTarget ) ) { iImpact = (INT32)((iImpact * 140 / 100) + 0.5); // 30% incresed damage on suprising attacks - } + } + } + + // Flugente: power armour reduces damage taken + INT8 iSlot = VESTPOS; + switch( pSoldier->bAimShotLocation ) + { + case AIM_SHOT_HEAD: + iSlot = HELMETPOS; + break; + case AIM_SHOT_LEGS: + iSlot = LEGPOS; + break; + default: + iSlot = VESTPOS; + break; } } // DAMAGE BONUS TO KNIFE ATTACK WITH MELEE SKILL @@ -9369,6 +9405,21 @@ INT32 HTHImpact( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTarget, INT32 iHitBy, BO iImpact = (INT32)(iImpact * 125 / 100); // 40% incresed damage to lying characters } } + + // Flugente: power armour reduces damage taken + INT8 iSlot = VESTPOS; + switch( pSoldier->bAimShotLocation ) + { + case AIM_SHOT_HEAD: + iSlot = HELMETPOS; + break; + case AIM_SHOT_LEGS: + iSlot = LEGPOS; + break; + default: + iSlot = VESTPOS; + break; + } } // bonus damage for aggressive characters @@ -9501,7 +9552,7 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi // We need to be agile and dexterous iAttRating = ( 2 * EffectiveDexterity( pAttacker ) + // coordination, accuracy * 2 * EffectiveAgility( pAttacker ) + // speed & reflexes - pAttacker->stats.bStrength + // physical strength + pAttacker->stats.bStrength + // physical strength (10 * EffectiveExpLevel( pAttacker ) ) ); // experience, knowledge } else diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h index 51fe4874..ba0ea583 100644 --- a/Tactical/Weapons.h +++ b/Tactical/Weapons.h @@ -211,6 +211,7 @@ typedef struct INT16 lockBustingPower; BOOLEAN tracerEffect; FLOAT temperatureModificator; // Flugente FTW 1.1: modificator for weapon temperature + INT16 poisonPercentage; // Flugente FTW 1.1: modificator for weapon temperature //zilpin: pellet spread patterns externalized in XML INT32 spreadPattern; @@ -428,6 +429,8 @@ extern void GetTargetWorldPositions( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, extern BOOLEAN OKFireWeapon( SOLDIERTYPE *pSoldier ); extern BOOLEAN CheckForGunJam( SOLDIERTYPE * pSoldier ); +extern FLOAT GetGunOverheatDamagePercentage( FLOAT usTemperature, UINT16 usIndx ); // Flugente FTW 1: Get percentage: temperature/damagethreshold +extern FLOAT GetGunOverheatJamPercentage( FLOAT usTemperature, UINT16 usIndx ); // Flugente FTW 1: Get percentage: temperature/jamthreshold extern INT32 CalcMaxTossRange( SOLDIERTYPE * pSoldier, UINT16 usItem, BOOLEAN fArmed ); extern UINT32 CalcThrownChanceToHit(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, UINT8 ubAimPos ); diff --git a/Tactical/World Items.cpp b/Tactical/World Items.cpp index e411f6b0..58627aa6 100644 --- a/Tactical/World Items.cpp +++ b/Tactical/World Items.cpp @@ -844,7 +844,7 @@ void CoolDownWorldItems( BOOLEAN fSetZero ) { for(INT16 i = 0; i < pObj->ubNumberOfObjects; ++i) // ... there might be multiple items here (item stack), so for each one ... { - FLOAT newguntemperature = 0.0; + FLOAT newguntemperature = 0.0f; if ( !fSetZero && gGameExternalOptions.fSetZeroUponNewSector ) { @@ -855,7 +855,7 @@ void CoolDownWorldItems( BOOLEAN fSetZero ) if ( Item[gWorldItems[ uiCount ].object.usItem].barrel == TRUE ) // ... a barrel lying around cools down a bit faster ... cooldownfactor *= gGameExternalOptions.iCooldownModificatorLonelyBarrel; - newguntemperature = max(0.0, guntemperature - cooldownfactor); // ... calculate new temperature ... + newguntemperature = max(0.0f, guntemperature - cooldownfactor); // ... calculate new temperature ... } (*pObj)[i]->data.bTemperature = newguntemperature; // ... set new temperature @@ -869,7 +869,7 @@ void CoolDownWorldItems( BOOLEAN fSetZero ) { if ( iter->exists() && Item[ iter->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) { - FLOAT newtemperature = 0.0; + FLOAT newtemperature = 0.0f; if ( !fSetZero && gGameExternalOptions.fSetZeroUponNewSector ) { @@ -877,7 +877,7 @@ void CoolDownWorldItems( BOOLEAN fSetZero ) FLOAT cooldownfactor = GetItemCooldownFactor( &(*iter) ); // ... get cooldown factor ... - newtemperature = max(0.0, temperature - cooldownfactor); // ... calculate new temperature ... + newtemperature = max(0.0f, temperature - cooldownfactor); // ... calculate new temperature ... } (*iter)[i]->data.bTemperature = newtemperature; // ... set new temperature diff --git a/Tactical/XML_AmmoTypes.cpp b/Tactical/XML_AmmoTypes.cpp index 33570c38..39e08e93 100644 --- a/Tactical/XML_AmmoTypes.cpp +++ b/Tactical/XML_AmmoTypes.cpp @@ -79,7 +79,8 @@ ammotypeStartElementHandle(void *userData, const XML_Char *name, const XML_Char strcmp(name, "acidic") == 0 || strcmp(name, "tracerEffect") == 0 || strcmp(name, "monsterSpit") == 0 || - strcmp(name, "temperatureModificator") == 0 )) + strcmp(name, "temperatureModificator") == 0 || + strcmp(name, "PoisonPercentage") == 0 )) { pData->curElement = ELEMENT_PROPERTY; @@ -282,6 +283,15 @@ ammotypeEndElementHandle(void *userData, const XML_Char *name) pData->curElement = ELEMENT; pData->curAmmoType.temperatureModificator = (FLOAT) atof(pData->szCharData); } + else if(strcmp(name, "PoisonPercentage") == 0) + { + pData->curElement = ELEMENT; + pData->curAmmoType.poisonPercentage = (INT16) atol(pData->szCharData); + + // check: poisonPercentage must be between 0 and 100, fit bad values + pData->curAmmoType.poisonPercentage = max(0, pData->curAmmoType.poisonPercentage); + pData->curAmmoType.poisonPercentage = min(100, pData->curAmmoType.poisonPercentage); + } pData->maxReadDepth--; } @@ -404,6 +414,7 @@ BOOLEAN WriteAmmoTypeStats() FilePrintf(hFile,"\t\t%d\r\n", AmmoTypes[cnt].lockBustingPower ); FilePrintf(hFile,"\t\t%d\r\n", AmmoTypes[cnt].tracerEffect ); FilePrintf(hFile,"\t\t%4.2f\r\n", AmmoTypes[cnt].temperatureModificator ); + FilePrintf(hFile,"\t\t%d\r\n", AmmoTypes[cnt].poisonPercentage ); FilePrintf(hFile,"\t\r\n"); diff --git a/Tactical/opplist.cpp b/Tactical/opplist.cpp index 633d269a..82a48018 100644 --- a/Tactical/opplist.cpp +++ b/Tactical/opplist.cpp @@ -4764,6 +4764,7 @@ 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; + case SOLDIER_CLASS_ZOMBIE: gprintf( 320, LINE_HEIGHT * ubLine, L"(Zombie)" ); break; default: break; //don't care (don't write anything) } ubLine++; diff --git a/Tactical/soldier profile type.h b/Tactical/soldier profile type.h index b12b0a7b..12e94043 100644 --- a/Tactical/soldier profile type.h +++ b/Tactical/soldier profile type.h @@ -262,6 +262,7 @@ public: UINT16 usKillsAdmins; UINT16 usKillsHostiles; UINT16 usKillsCreatures; + UINT16 usKillsZombies; UINT16 usKillsTanks; UINT16 usKillsOthers; diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index 7964c0f1..dc4275eb 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -171,6 +171,14 @@ INT32 ClosestSeenOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLeve void CreatureCall( SOLDIERTYPE * pCaller ); INT8 CreatureDecideAction( SOLDIERTYPE * pCreature ); void CreatureDecideAlertStatus( SOLDIERTYPE *pCreature ); + +// 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); + INT8 CrowDecideAction( SOLDIERTYPE * pSoldier ); void DecideAlertStatus( SOLDIERTYPE *pSoldier ); INT8 DecideAutoBandage( SOLDIERTYPE * pSoldier ); diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index cbaa8eca..42202199 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -1616,7 +1616,32 @@ fprintf(NetDebugFile,"\tNPC %d, dtctLvl %d, marking mine at gridno %d, gridCost void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) { + // 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; + // simple solution to prevent an endless clock: remember the last soldier that decided an action. If its the same one, increase the counter. + // if counter is high enough, end this guy's turn + if ( pSoldier == pLastDecisionSoldier ) + { + // we will only end our turn this way if this function was called over 100 times with same soldier without ending a turn. + // so many actions in a single turn smell of an endless clock. + // If we end a turn normally, the counter will be set back to 0, so this wont be a problem if you have a single soldier left for multiple turns + if ( lastdecisioncount >= 100 ) + { + // zombie is done doing harm... + EndAIGuysTurn( pSoldier); + lastdecisioncount = 0; + return ; + } + else + ++lastdecisioncount; + } + else + { + pLastDecisionSoldier = pSoldier; + lastdecisioncount = 0; + } /* if (Status.gamePaused) @@ -1905,7 +1930,11 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) { if (!(gTacticalStatus.uiFlags & ENGAGED_IN_CONV)) { - if (CREATURE_OR_BLOODCAT( pSoldier )) + if ( pSoldier->IsZombie() ) + { + pSoldier->aiData.bAction = ZombieDecideAction(pSoldier); + } + else if (CREATURE_OR_BLOODCAT( pSoldier )) { pSoldier->aiData.bAction = CreatureDecideAction( pSoldier ); } @@ -1971,6 +2000,7 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier) { // turn based... abort this guy's turn EndAIGuysTurn( pSoldier ); + lastdecisioncount = 0; } } else diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index dc3179ff..9853140d 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -1295,6 +1295,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++) + { + 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; + } + } + +#ifdef DEBUGDECISIONS + if (!TileIsOutOfBounds(sClosestOpponent)) + { + AINumMessage("CLOSEST OPPONENT is at gridno ",sClosestOpponent); + } +#endif + + if (psGridNo) + { + *psGridNo = sClosestOpponent; + } + if (pbLevel) + { + *pbLevel = bClosestLevel; + } + return( sClosestOpponent ); +} + + INT32 ClosestPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ) { // used by NPCs... find the closest PC @@ -2487,6 +2581,10 @@ UINT8 SoldierDifficultyLevel( SOLDIERTYPE * pSoldier ) bDifficulty = 4; break; + case SOLDIER_CLASS_ZOMBIE: + bDifficulty = bDifficultyBase; + break; + default: if (pSoldier->bTeam == CREATURE_TEAM) { diff --git a/TacticalAI/CreatureDecideAction.cpp b/TacticalAI/CreatureDecideAction.cpp index 0e22ccf0..d46e6cb8 100644 --- a/TacticalAI/CreatureDecideAction.cpp +++ b/TacticalAI/CreatureDecideAction.cpp @@ -1757,3 +1757,4 @@ INT8 CrowDecideAction( SOLDIERTYPE * pSoldier ) return( AI_ACTION_NONE ); } } + diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 0077aba4..85f8bd15 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -64,6 +64,10 @@ 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); void DoneScheduleAction( SOLDIERTYPE * pSoldier ) { @@ -699,6 +703,10 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) STR16 tempstr; #endif + // Flugente: to prevent an accidental call + if ( pSoldier->IsZombie() ) + return( ZombieDecideActionGreen(pSoldier) ); + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("DecideActionGreen, orders = %d",pSoldier->aiData.bOrders)); BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); @@ -1389,6 +1397,9 @@ INT8 DecideActionYellow(SOLDIERTYPE *pSoldier) #ifdef DEBUGDECISIONS STR16 tempstr; #endif + // Flugente: to prevent an accidental call + if ( pSoldier->IsZombie() ) + return( ZombieDecideActionYellow(pSoldier) ); if (fCivilian) { @@ -2093,6 +2104,10 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) (pSoldier->aiData.bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); + // Flugente: to prevent an accidental call + if ( pSoldier->IsZombie() ) + return( ZombieDecideActionRed(pSoldier, ubUnconsciousOK) ); + // 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 //INT16 ubOverallTacticalSituation = AssessTacticalSituation(pSoldier->bSide); @@ -3640,6 +3655,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) { // 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) ); + pSoldier->bPoisonBleeding = max(0, pSoldier->bPoisonBleeding - __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 @@ -3811,6 +3827,10 @@ 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)); + // Flugente: to prevent an accidental call + if ( pSoldier->IsZombie() ) + return( ZombieDecideActionBlack(pSoldier) ); + ATTACKTYPE BestShot = {},BestThrow = {},BestStab = {},BestAttack = {}; BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || pSoldier->aiData.bNeutral || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); UINT8 ubBestStance = 1, ubStanceCost; @@ -5912,3 +5932,2157 @@ 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 ) ) + { + 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_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); + + 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 (!gfTurnBasedAI) + { + // wait after this... + pSoldier->aiData.bNextAction = AI_ACTION_WAIT; + pSoldier->aiData.usNextActionData = RealtimeDelay( pSoldier ); + } + return(AI_ACTION_RANDOM_PATROL); + } + } + } + + 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); + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // 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 + //////////////////////////////////////////////////////////////////////////// + + // 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 ) + { + 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 + { + return(AI_ACTION_SEEK_OPPONENT); + } + } + 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 (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 + + 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 )) ) + { +#ifdef AI_TIMING_TESTS + uiStartTime = GetJA2Clock(); +#endif + +#ifdef AI_TIMING_TESTS + uiEndTime = GetJA2Clock(); + guiRedSeekTimeTotal += (uiEndTime - uiStartTime); + guiRedSeekCounter++; +#endif + // if there is an opponent reachable + if (!TileIsOutOfBounds(sClosestDisturbance) ) + { + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: seek opponent"); + ////////////////////////////////////////////////////////////////////// + // SEEK CLOSEST DISTURBANCE: GO DIRECTLY TOWARDS CLOSEST KNOWN OPPONENT + ////////////////////////////////////////////////////////////////////// + + // 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 DEBUGDECISIONS + // do it! + sprintf(tempstr,"%s - SEEKING OPPONENT at grid %d, MOVING to %d", + pSoldier->name,sClosestDisturbance,pSoldier->aiData.usActionData); + AIPopMessage(tempstr); +#endif + + if ( fClimb && gGameExternalOptions.fZombieCanClimb )//&& pSoldier->aiData.usActionData == sClosestDisturbance) + { + // 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) + { + 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 + { + // 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); + } + } + 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"); + + } + + + 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)) + { + ////////////////////////////////////////////////////////////////////// + // 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)) + { +#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) + { + // 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) + { + 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)) + { + //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); + } + } + + // 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; + } + + if ((INT16)PreRandom(100) < iChance && 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); + } + } + } + } + + if ( TANK( pSoldier ) ) + { + // try turning in a random direction as we still can't see anyone. + if (!gfTurnBasedAI || GetAPsToLook( pSoldier ) <= pSoldier->bActionPoints) + { + 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 ) + { + 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 ) + { + 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 ); + } + + + //////////////////////////////////////////////////////////////////////////// + // 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) + { + 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); + } + + // 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); + + 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; + } + else + { + do + { + + bCanAttack = CanNPCAttack(pSoldier); + if (bCanAttack != TRUE) + { + 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; + + // drop item + return AI_ACTION_DROP_ITEM; + } + else + { + 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; + + // 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) ) + { + + 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); + } + } + } + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // 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)) + { + // 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 = 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: + #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; + 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()) + { + // 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 fa38aa0c..a791dd6f 100644 --- a/TacticalAI/Realtime.cpp +++ b/TacticalAI/Realtime.cpp @@ -27,6 +27,10 @@ INT8 RTPlayerDecideAction( SOLDIERTYPE * pSoldier ) { bAction = DecideAutoBandage( pSoldier ); } + else if ( pSoldier->IsZombie() ) + { + bAction = ZombieDecideAction( pSoldier ); + } else { bAction = DecideAction( pSoldier ); @@ -47,6 +51,10 @@ INT8 RTDecideAction(SOLDIERTYPE *pSoldier) { return( CreatureDecideAction( pSoldier ) ); } + else if ( pSoldier->IsZombie() ) + { + return( ZombieDecideAction( pSoldier ) ); + } else if (pSoldier->ubBodyType == CROW) { return( CrowDecideAction( pSoldier ) ); diff --git a/TileEngine/Explosion Control.cpp b/TileEngine/Explosion Control.cpp index 4fa43c5e..0ce0af6e 100644 --- a/TileEngine/Explosion Control.cpp +++ b/TileEngine/Explosion Control.cpp @@ -1577,21 +1577,10 @@ BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT32 sBombGridNo // Bodybuilding damage resistance if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pSoldier, BODYBUILDING_NT ) ) sNewWoundAmt = max( 1, (INT16)(sNewWoundAmt * (100 - gSkillTraitValues.ubBBDamageResistance) / 100)); - - // 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); + + // Flugente: moved the damage calculation into a separate function + if ( pSoldier->GetDamageResistance(FALSE, FALSE) > 0 ) + sNewWoundAmt -= ((sNewWoundAmt * pSoldier->GetDamageResistance(FALSE, FALSE)) /100); // we can loose stats due to being hit by the blast else if ( gGameOptions.fNewTraitSystem && Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL && @@ -1790,41 +1779,12 @@ BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT32 sBombGridNo sNewWoundAmt = max(1, sNewWoundAmt); } ////////////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////////// - // 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; - } - } - + // SANDRO - new merc records - times wounded (blasted by explosion) if ( ( sNewWoundAmt > 1 || sBreathAmt > 1000 ) && pSoldier->ubProfile != NO_PROFILE ) gMercProfiles[ pSoldier->ubProfile ].records.usTimesWoundedBlasted++; //////////////////////////////////////////////////////////////////////////////////// - - + // Lesh: flashbang does affect on soldier or not - check it if ( (Item[usItem].usItemClass & IC_EXPLOSV) && fFlashbang ) { @@ -1918,7 +1878,8 @@ BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, IN { if ( pExplosive->ubType == EXPLOSV_TEARGAS ) { - if ( AM_A_ROBOT( pSoldier ) ) + // zombies and robots are unaffected by tear gas + if ( AM_A_ROBOT( pSoldier ) || pSoldier->IsZombie() ) { return( fRecompileMovementCosts ); } @@ -1932,7 +1893,8 @@ BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, IN } else if ( pExplosive->ubType == EXPLOSV_MUSTGAS ) { - if ( AM_A_ROBOT( pSoldier ) ) + // zombies and robots are unaffected by mustard gas + if ( AM_A_ROBOT( pSoldier ) || pSoldier->IsZombie() ) { return( fRecompileMovementCosts ); } @@ -2030,9 +1992,8 @@ BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, IN case EXPLOSV_TEARGAS: pSoldier->flags.fHitByGasFlags |= HIT_BY_TEARGAS; break; - case EXPLOSV_MUSTGAS: + case EXPLOSV_MUSTGAS: pSoldier->flags.fHitByGasFlags |= HIT_BY_MUSTARDGAS; - break; case EXPLOSV_BURNABLEGAS: pSoldier->flags.fHitByGasFlags |= HIT_BY_BURNABLEGAS; break; @@ -2046,7 +2007,7 @@ BOOLEAN DishOutGasDamage( SOLDIERTYPE * pSoldier, EXPLOSIVETYPE * pExplosive, IN //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"ExpControl pSoldier->flags.fHitByGasFlags: %d", pSoldier->flags.fHitByGasFlags ); // a gas effect, take damage directly... - pSoldier->SoldierTakeDamage( ANIM_STAND, sWoundAmt, sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_STAND, sWoundAmt, 0, sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE ); if (is_networked && is_client) { @@ -2480,7 +2441,7 @@ BOOLEAN ExpAffect( INT32 sBombGridNo, INT32 sGridNo, UINT32 uiDist, UINT16 usIte break; } // a gas effect, take damage directly... - pSoldier->SoldierTakeDamage( ANIM_STAND, sWoundAmt, sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE ); + pSoldier->SoldierTakeDamage( ANIM_STAND, sWoundAmt, 0, sBreathAmt, TAKE_DAMAGE_GAS, NOBODY, NOWHERE, 0, TRUE ); if ( pSoldier->stats.bLife >= CONSCIOUSNESS ) { pSoldier->DoMercBattleSound( (INT8)( BATTLE_SOUND_HIT1 + Random( 2 ) ) ); diff --git a/TileEngine/Tactical Placement GUI.cpp b/TileEngine/Tactical Placement GUI.cpp index 29fde93c..16cd7348 100644 --- a/TileEngine/Tactical Placement GUI.cpp +++ b/TileEngine/Tactical Placement GUI.cpp @@ -683,14 +683,43 @@ void RenderTacticalPlacementGUI() iStartY = yp + 29 - 27*pSoldier->stats.bLifeMax/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+36, iStartY, xp+37, yp+29, Get16BPPColor( FROMRGB( 107, 107, 57 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+37, iStartY, xp+38, yp+29, Get16BPPColor( FROMRGB( 222, 181, 115 ) ) ); + + // poisoned bleeding in purple + if ( pSoldier->bPoisonBleeding ) + { + iStartY = yp + 29 - 27*(pSoldier->stats.bLifeMax - pSoldier->bBleeding + pSoldier->bPoisonBleeding)/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+36, iStartY, xp+37, yp+29, Get16BPPColor( FROMRGB( 107, 57, 107 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+37, iStartY, xp+38, yp+29, Get16BPPColor( FROMRGB( 222, 115, 181 ) ) ); + } + //pink one for bandaged. - iStartY += 27*pSoldier->bBleeding/100; + iStartY = yp + 29 - 27*(pSoldier->stats.bLifeMax - pSoldier->bBleeding)/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+36, iStartY, xp+37, yp+29, Get16BPPColor( FROMRGB( 156, 57, 57 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+37, iStartY, xp+38, yp+29, Get16BPPColor( FROMRGB( 222, 132, 132 ) ) ); + + // get amount of poisoned bandage + INT8 bPoisonBandage = pSoldier->bPoisonSum - pSoldier->bPoisonBleeding - pSoldier->bPoisonLife; + if ( bPoisonBandage ) + { + // poisoned bandage in bright green + iStartY = yp + 29 - 27*(pSoldier->stats.bLife + bPoisonBandage)/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+36, iStartY, xp+37, yp+29, Get16BPPColor( FROMRGB( 57, 156, 57 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+37, iStartY, xp+38, yp+29, Get16BPPColor( FROMRGB( 132, 222, 132 ) ) ); + } + //red one for actual health iStartY = yp + 29 - 27*pSoldier->stats.bLife/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+36, iStartY, xp+37, yp+29, Get16BPPColor( FROMRGB( 107, 8, 8 ) ) ); ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+37, iStartY, xp+38, yp+29, Get16BPPColor( FROMRGB( 206, 0, 0 ) ) ); + + // poisoned life + if ( pSoldier->bPoisonLife ) + { + iStartY = yp + 29 - 27*pSoldier->bPoisonLife/100; + ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+36, iStartY, xp+37, yp+29, Get16BPPColor( FROMRGB( 8, 107, 8 ) ) ); + ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+37, iStartY, xp+38, yp+29, Get16BPPColor( FROMRGB( 0, 206, 0 ) ) ); + } + //BREATH BAR iStartY = yp + 29 - 27*pSoldier->bBreathMax/100; ColorFillVideoSurfaceArea( FRAME_BUFFER, xp+39, iStartY, xp+40, yp+29, Get16BPPColor( FROMRGB( 8, 8, 132 ) ) ); diff --git a/Utils/Text.h b/Utils/Text.h index bdc136a1..a8a8b028 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -632,14 +632,14 @@ extern STR16 szUDBGenWeaponsStatsTooltipText[ 23 ]; extern STR16 szUDBGenWeaponsStatsExplanationsTooltipText[ 23 ]; extern STR16 szUDBGenArmorStatsTooltipText[ 3 ]; extern STR16 szUDBGenArmorStatsExplanationsTooltipText[ 3 ]; -extern STR16 szUDBGenAmmoStatsTooltipText[ 4 ]; // Flugente Overheating Weapons: 3->4 -extern STR16 szUDBGenAmmoStatsExplanationsTooltipText[ 4 ]; +extern STR16 szUDBGenAmmoStatsTooltipText[ 5 ]; // Flugente Overheating: 3->4 poison: 4->5 +extern STR16 szUDBGenAmmoStatsExplanationsTooltipText[ 5 ]; // Flugente Overheating: 3->4 poison: 4->5 extern STR16 szUDBGenExplosiveStatsTooltipText[ 22 ]; extern STR16 szUDBGenExplosiveStatsExplanationsTooltipText[ 22 ]; extern STR16 szUDBGenSecondaryStatsTooltipText[ 26 ]; extern STR16 szUDBGenSecondaryStatsExplanationsTooltipText[ 26 ]; -extern STR16 szUDBAdvStatsTooltipText[ 56 ]; // Flugente Overheating Weapons: 48->56 -extern STR16 szUDBAdvStatsExplanationsTooltipText[ 56 ]; // Flugente Overheating Weapons: 48->56 +extern STR16 szUDBAdvStatsTooltipText[ 57 ]; // Flugente Overheating Weapons: 48->56 poison: 56->57 +extern STR16 szUDBAdvStatsExplanationsTooltipText[ 57 ]; // Flugente Overheating Weapons: 48->56 poison: 56->57 extern STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[ 52 ]; // Flugente Overheating Weapons: 48->52 // Headrock: End Externs @@ -752,6 +752,7 @@ enum CREATURE_TEAM_MERC_NAME, MILITIA_TEAM_MERC_NAME, CIV_TEAM_MERC_NAME, + ZOMBIE_TEAM_MERC_NAME, //The text for the 'exiting sector' gui EXIT_GUI_TITLE_STR, @@ -808,6 +809,8 @@ enum CHOOSE_REMOTE_DETONATE_AND_REMOTE_DEFUSE_FREQUENCY_STR, CHOOSE_DETONATE_AND_REMOTE_DEFUSE_FREQUENCY_STR, CHOOSE_TRIPWIRE_NETWORK, + + MERC_VITAL_STATS_WITH_POISON_POPUPTEXT, TEXT_NUM_TACTICAL_STR }; diff --git a/Utils/XML_Items.cpp b/Utils/XML_Items.cpp index b840c692..54df5473 100644 --- a/Utils/XML_Items.cpp +++ b/Utils/XML_Items.cpp @@ -265,6 +265,8 @@ itemStartElementHandle(void *userData, const XML_Char *name, const XML_Char **at strcmp(name, "Directional") == 0 || strcmp(name, "DrugType") == 0 || strcmp(name, "BlockIronSight") == 0 || + // Flugente: poison system + strcmp(name, "PoisonPercentage") == 0 || strcmp(name, "fFlags") == 0 )) { @@ -1331,6 +1333,12 @@ itemEndElementHandle(void *userData, const XML_Char *name) pData->curElement = ELEMENT; pData->curItem.blockironsight = (BOOLEAN) atol(pData->szCharData); } + // Flugente poison system + else if(strcmp(name, "PoisonPercentage") == 0) + { + pData->curElement = ELEMENT; + pData->curItem.bPoisonPercentage = (INT16) atol(pData->szCharData); + } pData->maxReadDepth--; @@ -1947,6 +1955,9 @@ BOOLEAN WriteItemStats() FilePrintf(hFile,"\t\t%d\r\n", Item[cnt].blockironsight ); + // Flugente poison system + FilePrintf(hFile,"\t\t%d\r\n", Item[cnt].bPoisonPercentage ); + FilePrintf(hFile,"\t\r\n"); } FilePrintf(hFile,"\r\n"); diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 7c0fc124..b905ec1a 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -2070,6 +2070,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"弱点:", L"态度:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2865,6 +2867,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"异形", L"民兵", L"平民", + L"ZOMBIE", // TODO.Translate L"离开分区", L"确定", L"取消", @@ -2920,6 +2923,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3473,6 +3479,7 @@ STR16 pMapScreenStatusStrings[] = L"士气", L"状态", // the condition of the current vehicle (its "health") L"油量", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5102,8 +5109,9 @@ STR16 zOptionsToggleText[] = L"显示脸部装备图", L"显示脸部装备图标", L"禁止光标切换", // Disable Cursor Swap - L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate + L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"自动加速敌军回合", // Automatic fast forward through AI turns + L"Allow Zombies", // TODO.Translate //L"武器过热", L"--作弊模式选项--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"强制 Bobby Ray 送货", // force all pending Bobby Ray shipments @@ -5213,6 +5221,7 @@ STR16 zOptionsScreenHelpText[] = L"打开时,在交换位置和其它动作时光标不切换。键入 |x 可以快速切换。", L"When ON, mercs will not report progress during training.", //TODO.Translate L"打开时,敌军回合将被大幅加速。", + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"打开时,武器连续射击时温度逐渐升高引起过热。", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"强制 Bobby Ray 出货", @@ -6924,6 +6933,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|翻|搅|力|(|开|花|弹|)", L"|爆|炸|力|(|炸|子|儿|)", L"|过|热|修|正", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6932,6 +6942,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \n该值决定子弹打穿护甲击中身体时的伤害力\n加成。\n \n该值大于1时,子弹在穿过护甲后会增加伤害。\n \n当该值小于1时,子弹穿过护甲后会减少伤害。\n \n该数值越高越好。", L"\n \n该值是子弹在击中目标前已经造成的潜在伤害的倍率。\n \n大于1的数值可以增加伤害,\n反之则减少伤害。\n \n该数值越高越好。", L"\n \n子弹温度系数。\n \n该数值越低越好。", + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7108,6 +7119,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|冷|却|参|数", L"|卡|壳|阈|值", L"|损|坏|阈|值", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7169,6 +7181,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \n武器的冷却系数数增加了(百分比)。\n \n该数值越高越好。", L"\n \n武器的卡壳阈值增加了(百分比)。\n \n该数值越高越好。", L"\n \n武器的损坏阈值增加了(百分比)。\n \n该数值越高越好。", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index a91f3100..7987ede4 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -2066,6 +2066,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Weakness:", L"Attitudes:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2862,6 +2864,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"CREPITUS", // L"CREPITUS", L"MILITIE", // L"MILITIA", L"BURGER", // L"CIVILIAN", + L"ZOMBIE", // TODO.Translate L"Verlaten Sector", // L"Exiting Sector", L"OK", L"Stoppen", // L"Cancel", @@ -2917,6 +2920,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3469,6 +3475,7 @@ STR16 pMapScreenStatusStrings[] = L"Moraal", L"Conditie", // the condition of the current vehicle (its "health") L"Brandstof", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5103,6 +5110,7 @@ STR16 zOptionsToggleText[] = L"Uit te schakelen Cursor Swap", // Disable Cursor Swap L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate + L"Allow Zombies", // TODO.Translate //L"Weapons Overheating", // TODO.Translate L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments @@ -5212,6 +5220,7 @@ STR16 zOptionsScreenHelpText[] = L"Wanneer ingeschakeld, zal de cursor niet te schakelen tussen uitwisseling positie en andere acties. Druk op |x om snelle uitwisseling te starten.", L"When ON, mercs will not report progress during training.", //TODO.Translate L"When ON, AI turns will be much faster.", // TODO.Translate + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", @@ -6919,6 +6928,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|B|u|l|l|e|t |T|u|m|b|l|e", L"|P|r|e|-|I|m|p|a|c|t |E|x|p|l|o|s|i|o|n", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", // TODO.Translate + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6927,6 +6937,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDetermines a proportional increase of damage\npotential once the bullet gets through the\ntarget's armor and hits the bodypart behind it.\n \nWhen above 1.0, the bullet's damage\nincreases after penetrating the armor.\n \nWhen below 1.0, the bullet's damage\npotential decreases after passing through armor.\n \nHigher is better.", L"\n \nA multiplier to the bullet's damage potential\nthat is applied immediately before hitting the\ntarget.\n \nValues above 1.0 indicate an increase in damage,\nvalues below 1.0 indicate a decrease.\n \nHigher is better.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", // TODO.Translate + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7103,6 +7114,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7164,6 +7176,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 4d9833d6..e17afaca 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -2070,6 +2070,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Weakness:", L"Attitudes:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // Flugente Zombies }; @@ -2865,6 +2867,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"CREPITUS", L"MILITIA", L"CIVILIAN", + L"ZOMBIE", L"Exiting Sector", L"OK", L"Cancel", @@ -2920,6 +2923,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3471,6 +3477,7 @@ STR16 pMapScreenStatusStrings[] = L"Morale", L"Condition", // the condition of the current vehicle (its "health") L"Fuel", // the fuel level of the current vehicle (its "energy") + L"Poison", // for display of poisoning }; @@ -5102,6 +5109,7 @@ STR16 zOptionsToggleText[] = L"Disable Cursor Swap", // Disable Cursor Swap L"Quiet Training", // Madd: mercs don't say quotes while training L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns + L"Allow Zombies", // Flugente Zombies 1.0 //L"Weapons Overheating", // TODO.Translate L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments @@ -5113,7 +5121,7 @@ STR16 zOptionsToggleText[] = L"Debug Options in other builds", // allow debugging in release or mapeditor L"DEBUG Render Option group", // an example option that will show/hide other options L"Render Mouse Regions", // an example of a DEBUG build option - L"-----------------", // an example options screen options divider (pure text) + L"-----------------", // an example options screen options divider (pure text) // this is THE LAST option that exists (debug the options screen, doesnt do anything, except exist) L"THE_LAST_OPTION", @@ -5211,6 +5219,7 @@ STR16 zOptionsScreenHelpText[] = L"When ON, the cursor will not toggle between exchange position and other actions. Press |x to initiate quick exchange.", L"When ON, mercs will not report progress during training.", L"When ON, AI turns will be much faster.", + L"When ON, zombies will span. Be aware!", // allow zombies //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", @@ -5222,8 +5231,7 @@ STR16 zOptionsScreenHelpText[] = L"Allows debug options in release or mapeditor builds", // allow debugging in release or mapeditor L"Toggle to display debugging render options", // an example option that will show/hide other options L"Attempts to display slash-rects around mouse regions", // an example of a DEBUG build option - L"(text not rendered)TOPTION_DEBUG_MODE_OPTIONS_END", // an example options screen options divider (pure text) - + L"(text not rendered)TOPTION_DEBUG_MODE_OPTIONS_END", // an example options screen options divider (pure text) // this is THE LAST option that exists (debug the options screen, doesnt do anything, except exist) L"TOPTION_LAST_OPTION", @@ -6911,6 +6919,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|B|u|l|l|e|t |T|u|m|b|l|e", L"|P|r|e|-|I|m|p|a|c|t |E|x|p|l|o|s|i|o|n", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6919,6 +6928,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDetermines a proportional increase of damage\npotential once the bullet gets through the\ntarget's armor and hits the bodypart behind it.\n \nWhen above 1.0, the bullet's damage\nincreases after penetrating the armor.\n \nWhen below 1.0, the bullet's damage\npotential decreases after passing through armor.\n \nHigher is better.", L"\n \nA multiplier to the bullet's damage potential\nthat is applied immediately before hitting the\ntarget.\n \nValues above 1.0 indicate an increase in damage,\nvalues below 1.0 indicate a decrease.\n \nHigher is better.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7096,6 +7106,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7157,6 +7168,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index a89d89e7..d192cbb1 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -2069,6 +2069,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Faiblesse :", L"Attitudes :", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2864,6 +2866,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"CREPITUS", L"Milice", L"CIVIL", + L"ZOMBIE", // TODO.Translate L"Quitter Secteur", L"OK", L"Annuler", @@ -2919,6 +2922,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3473,6 +3479,7 @@ STR16 pMapScreenStatusStrings[] = L"Moral", L"Etat", // the condition of the current vehicle (its "Santé") L"Carburant", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5093,6 +5100,7 @@ STR16 zOptionsToggleText[] = L"Désactiver échange curseur", // Disable Cursor Swap L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate + L"Allow Zombies", // Flugente Zombies 1.0 //L"Weapons Overheating", // TODO.Translate L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Forcer envois Bobby Ray", // force all pending Bobby Ray shipments @@ -5202,6 +5210,7 @@ STR16 zOptionsScreenHelpText[] = L"Si activé, le curseur ne basculera pas entre changer de position et une autre action. Appuyez sur |x pour initier un échange rapide.", L"When ON, mercs will not report progress during training.", L"When ON, AI turns will be much faster.", // TODO.Translate + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Forcer tous les envois en attente de Bobby Ray", @@ -6894,6 +6903,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|C|u|l|b|u|t|e |d|e |b|a|l|l|e", L"|P|r|e|-|I|m|p|a|c|t |E|x|p|l|o|s|i|o|n", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", // TODO.Translate + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6902,6 +6912,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDétermine le potentiel de la balle à faire des dégâts\nsur le corps après avoir traversée l'armure.\n \nAvec une valeur supérieure à 1.0, la balle fera de lourds dégâts\naprès pénétration\nAvec une valeur inférieure à 1.0, la balle fera des dégâts moindre\naprès pénétration.\n \nValeur élevée recommandée.", L"\n \nMultiplicateur de potentiel de dégâts juste avant\nl'impact de la balle.\n \nAvec une valeur supérieure à 1.0, la balle fera de lourds dégâts\nUne valeur inférieure à 1.0 fera des dégâts moindre.\n \nValeur élevée recommandée.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", // TODO.Translate + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7078,6 +7089,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7139,6 +7151,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index 3a0f0340..0658396a 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -2080,6 +2080,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Schwächen:", L"Persönlichkeit:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2866,6 +2868,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"MONSTER", L"MILIZ", L"ZIVILIST", + L"ZOMBIE", // TODO.Translate L"Sektor verlassen", L"OK", L"Abbruch", @@ -2923,6 +2926,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Detonations-Frequenz (1 - 4) und die Entschärfungs-Frequenz (A - D):", L"Detonations-Zeit (in Züge) (1 - 4) und die Entschärfungs-Frequenz (A - D):", L"Stolperdraht-Hierarchie (1 - 4) und das Netzwerk (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3440,6 +3446,7 @@ STR16 pMapScreenStatusStrings[] = L"Moral", L"Zustand", // the condition of the current vehicle (its "health") L"Tank", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; STR16 pMapScreenPrevNextCharButtonHelpText[] = @@ -4951,6 +4958,7 @@ STR16 zOptionsToggleText[] = L"Cursor-Wechsel deaktivieren", // Disable Cursor Swap L"Stummes Trainieren", // Madd: mercs don't say quotes while training L"Autom. schnelle Gegner-Züge", // Automatic fast forward through AI turns + L"Allow Zombies", // Flugente Zombies 1.0 //L"Waffen können überhitzen", L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Erzwinge BR Lieferung", // force all pending Bobby Ray shipments @@ -6739,6 +6747,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|K|u|g|e|l|s|t|u|r|z", L"|E|x|p|l|o|s|i|o|n |v|o|r |E|i|n|s|c|h|l|a|g", L"|T|e|m|p|e|r|a|t|u|r |M|o|d|i|f|i|k|a|t|o|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6747,6 +6756,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nBestimmt eine verhältnismäßige Zunahme des Schadenspotentials,\nsobald die Kugel die Rüstung des Ziels\ndurchbricht und den Körper dahinter trifft.\n \nWerte über 1.0 erhöhen, Werte unter 1.0 reduzieren das Schadenspotential\nder durchbrochenen Kugel.\n \nHöher ist besser.", L"\n \nEin Multiplikator zum Schadenspotential der Kugel,\nder vor dem Treffen des Zieles angewandt wird.\n \nWerte über 1.0 erhöhen, Werte unter 1.0 reduzieren den Schaden.\n \nHöher ist besser.", L"\n \nProzentuale zusätzliche Hitze\ndurch diese Munitionsart.\n \nNiedriger ist besser.", + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -6923,6 +6933,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|M|o|d|i|f|i|k|a|t|o|r|-|A|b|k|ü|h|l|f|a|k|t|o|r", L"|M|o|d|i|f|i|k|a|t|o|r|-|L|a|d|e|h|e|m|m|u|n|g|s|s|c|h|r|a|n|k|e", L"|M|o|d|i|f|i|k|a|t|o|r|-|H|i|t|z|e|s|c|h|a|d|e|n|s|s|c|h|r|a|n|k|e", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -6984,6 +6995,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nAbkühlung wird um diesen Prozentsatz erhöht.\n \nHöher ist besser.", L"\n \nLadehemmungsschranke wird um diesen Prozentsatz erhöht.\n \nHöher ist besser.", L"\n \nHitzeschadensschranke wird um diesen Prozentsatz erhöht.\n \nHöher is besser.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index 1c88c7b0..7f72c6fd 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -2062,6 +2062,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Weakness:", L"Attitudes:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2857,6 +2859,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"CREPITUS", L"ESERCITO", L"CIVILE", + L"ZOMBIE", // TODO.Translate L"Settore di uscita", L"OK", L"Annulla", @@ -2912,6 +2915,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3464,6 +3470,7 @@ STR16 pMapScreenStatusStrings[] = L"Morale", L"Condizione", // the condition of the current vehicle (its "health") L"Carburante", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5088,6 +5095,7 @@ STR16 zOptionsToggleText[] = L"Disabilita Swap Cursore", // Disable Cursor Swap L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate + L"Allow Zombies", // Flugente Zombies 1.0 //L"Weapons Overheating", // TODO.Translate L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments @@ -5197,6 +5205,7 @@ STR16 zOptionsScreenHelpText[] = L"Se attivato, il cursore non si alternerà tra la posizione di scambio e altre azioni. Premere |x per avviare lo scambio rapido.", L"When ON, mercs will not report progress during training.", L"When ON, AI turns will be much faster.", // TODO.Translate + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", @@ -6907,6 +6916,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|B|u|l|l|e|t |T|u|m|b|l|e", L"|P|r|e|-|I|m|p|a|c|t |E|x|p|l|o|s|i|o|n", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", // TODO.Translate + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6915,6 +6925,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDetermines a proportional increase of damage\npotential once the bullet gets through the\ntarget's armor and hits the bodypart behind it.\n \nWhen above 1.0, the bullet's damage\nincreases after penetrating the armor.\n \nWhen below 1.0, the bullet's damage\npotential decreases after passing through armor.\n \nHigher is better.", L"\n \nA multiplier to the bullet's damage potential\nthat is applied immediately before hitting the\ntarget.\n \nValues above 1.0 indicate an increase in damage,\nvalues below 1.0 indicate a decrease.\n \nHigher is better.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", // TODO.Translate + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7091,6 +7102,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7152,6 +7164,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index af3ab5cd..90d4a9b3 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -2077,6 +2077,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"NiepełnosprawnoŚć:", L"Attitudes:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2872,6 +2874,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"STWÓR", L"SAMOOBRONA", L"CYWIL", + L"ZOMBIE", // TODO.Translate L"WyjŚcie z sektora", L"OK", L"Anuluj", @@ -2927,6 +2930,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3480,6 +3486,7 @@ STR16 pMapScreenStatusStrings[] = L"Morale", L"Stan", // the condition of the current vehicle (its "health") L"Paliwo", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5100,6 +5107,7 @@ STR16 zOptionsToggleText[] = L"Disable Cursor Swap", // Disable Cursor Swap // TODO.Translate L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate + L"Allow Zombies", // Flugente Zombies 1.0 //L"Weapons Overheating", // TODO.Translate L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments @@ -5209,6 +5217,7 @@ STR16 zOptionsScreenHelpText[] = L"When ON, the cursor will not toggle between exchange position and other actions. Press |x to initiate quick exchange.", // TODO.Translate L"When ON, mercs will not report progress during training.", L"When ON, AI turns will be much faster.", // TODO.Translate + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"WymuŚ wszystkie oczekiwane dostawy od Bobby Ray's.", @@ -6917,6 +6926,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|B|u|l|l|e|t |T|u|m|b|l|e", L"|P|r|e|-|I|m|p|a|c|t |E|x|p|l|o|s|i|o|n", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", // TODO.Translate + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6925,6 +6935,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDetermines a proportional increase of damage\npotential once the bullet gets through the\ntarget's armor and hits the bodypart behind it.\n \nWhen above 1.0, the bullet's damage\nincreases after penetrating the armor.\n \nWhen below 1.0, the bullet's damage\npotential decreases after passing through armor.\n \nHigher is better.", L"\n \nA multiplier to the bullet's damage potential\nthat is applied immediately before hitting the\ntarget.\n \nValues above 1.0 indicate an increase in damage,\nvalues below 1.0 indicate a decrease.\n \nHigher is better.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", // TODO.Translate + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7101,6 +7112,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7162,6 +7174,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index bbcea0b0..1f8ed5a1 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -2070,6 +2070,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Недостаток:", L"По жизни:", //Attitudes For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2865,6 +2867,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"РЕПТИОН", L"ОПОЛЧЕНЕЦ", L"ЖИТЕЛЬ", + L"ZOMBIE", // TODO.Translate L"Выход из сектора", L"ДА", L"ОТМЕНА", @@ -2920,6 +2923,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3473,6 +3479,7 @@ STR16 pMapScreenStatusStrings[] = L"Боевой дух", L"Состояние", // the condition of the current vehicle (its "health") L"Бензин", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5083,6 +5090,7 @@ STR16 zOptionsToggleText[] = L"Disable Cursor Swap", // Disable Cursor Swap // TODO.Translate L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate + L"Allow Zombies", // Flugente Zombies 1.0 //L"Weapons Overheating", // TODO.Translate L"--Читерские настройки--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Ускорить доставку Бобби Рэя", // force all pending Bobby Ray shipments @@ -5192,6 +5200,7 @@ STR16 zOptionsScreenHelpText[] = L"When ON, the cursor will not toggle between exchange position and other actions. Press |x to initiate quick exchange.", // TODO.Translate L"When ON, mercs will not report progress during training.", L"When ON, AI turns will be much faster.", // TODO.Translate + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Выберите этот пункт чтобы груз Бобби Рэя прибыл немедленно.", @@ -6885,6 +6894,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|П|а|д|е|н|и|е |п|а|т|р|о|н|ы", L"|П|р|е|д|в|а|р|и|т|е|л|ь|н|о|е |в|л|и|я|н|и|е |в|з|р|ы|в|а", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", // TODO.Translate + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6893,6 +6903,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDetermines a proportional increase of damage\npotential once the bullet gets through the\ntarget's armor and hits the bodypart behind it.\n \nWhen above 1.0, the bullet's damage\nincreases after penetrating the armor.\n \nWhen below 1.0, the bullet's damage\npotential decreases after passing through armor.\n \nHigher is better.", L"\n \nA multiplier to the bullet's damage potential\nthat is applied immediately before hitting the\ntarget.\n \nValues above 1.0 indicate an increase in damage,\nvalues below 1.0 indicate a decrease.\n \nHigher is better.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", // TODO.Translate + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7069,6 +7080,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7130,6 +7142,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]= diff --git a/Utils/_TaiwaneseText.cpp b/Utils/_TaiwaneseText.cpp index b1270b56..b9b162b4 100644 --- a/Utils/_TaiwaneseText.cpp +++ b/Utils/_TaiwaneseText.cpp @@ -2070,6 +2070,8 @@ STR16 pPersonnelRecordsHelpTexts[] = L"Weakness:", L"Attitudes:", // WANNE: For old traits display instead of "Character:"! + + L"Zombies: %d\n", // TODO.Translate }; @@ -2866,6 +2868,7 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"CREPITUS", L"MILITIA", L"CIVILIAN", + L"ZOMBIE", // TODO.Translate L"Exiting Sector", L"OK", L"Cancel", @@ -2921,6 +2924,9 @@ CHAR16 TacticalStr[][ MED_STRING_LENGTH ] = L"Set detonation frequency (1 - 4) and defusing frequency (A - D):", L"Set detonation time in turns (1 - 4) and defusing frequency (A - D):", L"Select tripwire hierarchy (1 - 4) and network (A - D):", + + // added by Flugente to display health and poisoning // TODO.Translate + L"Health: %d/%d\n Poison: %d/%d\nEnergy: %d/%d\nMorale: %s", }; //Varying helptext explains (for the "Go to Sector/Map" checkbox) what will happen given different circumstances in the "exiting sector" interface. @@ -3474,6 +3480,7 @@ STR16 pMapScreenStatusStrings[] = L"Morale", L"Condition", // the condition of the current vehicle (its "health") L"Fuel", // the fuel level of the current vehicle (its "energy") + L"Posion", // TODO.Translate }; @@ -5104,6 +5111,7 @@ STR16 zOptionsToggleText[] = L"Disable Cursor Swap", // Disable Cursor Swap L"Quiet Training", // Madd: mercs don't say quotes while training // TODO.Translate L"Auto Fast Forward AI Turns", // Automatic fast forward through AI turns // TODO.Translate + L"Allow Zombies", // Flugente Zombies 1.0 //L"Weapons Overheating", // TODO.Translate L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments @@ -5213,6 +5221,7 @@ STR16 zOptionsScreenHelpText[] = L"When ON, the cursor will not toggle between exchange position and other actions. Press |x to initiate quick exchange.", L"When ON, mercs will not report progress during training.", L"When ON, AI turns will be much faster.", // TODO.Translate + L"When ON, zombies will span. Be aware!", // allow zombies // TODO.Translate //L"When ON, weapons can be overheated when firing.", // TODO.Translate L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", @@ -6922,6 +6931,7 @@ STR16 szUDBGenAmmoStatsTooltipText[]= L"|B|u|l|l|e|t |T|u|m|b|l|e", L"|P|r|e|-|I|m|p|a|c|t |E|x|p|l|o|s|i|o|n", L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|i|o|n", // TODO.Translate + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= @@ -6930,6 +6940,7 @@ STR16 szUDBGenAmmoStatsExplanationsTooltipText[]= L"\n \nDetermines a proportional increase of damage\npotential once the bullet gets through the\ntarget's armor and hits the bodypart behind it.\n \nWhen above 1.0, the bullet's damage\nincreases after penetrating the armor.\n \nWhen below 1.0, the bullet's damage\npotential decreases after passing through armor.\n \nHigher is better.", L"\n \nA multiplier to the bullet's damage potential\nthat is applied immediately before hitting the\ntarget.\n \nValues above 1.0 indicate an increase in damage,\nvalues below 1.0 indicate a decrease.\n \nHigher is better.", L"\n \nAdditional heat generated by this ammunition.\n \nLower is better.", // TODO.Translate + L"\n \nDetermines what percentage of a\nbullet's damage will be poisonous.", // TODO.Translate }; STR16 szUDBGenExplosiveStatsTooltipText[]= @@ -7106,6 +7117,7 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|P|o|i|s|o|n |P|e|r|c|e|n|t|a|g|e", // TODO.Translate }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7167,6 +7179,7 @@ STR16 szUDBAdvStatsExplanationsTooltipText[]= L"\n \nA gun's cooldown factor is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's jam threshold is\nincreased by this percentage.\n \nHigher is better.", L"\n \nA gun's damage threshold is\nincreased by this percentage.\n \nHigher is better.", + L"\n \nThis is the percentage of damage dealt\nby this item that will be poisonous.\n\nUsefulness depends on wether enemy\nhas poison resistance or absorption.", // TODO.Translate }; STR16 szUDBAdvStatsExplanationsTooltipTextForWeapons[]=