Author SHA1 Message Date
Asdow 7b8c2d1ff9 Remove old function call 2026-01-23 23:35:59 +02:00
Asdow caf925b216 Fix Fatima not moving after map traversal 2026-01-23 23:35:03 +02:00
Asdow bd9cbd9944 Migrate +AI features
* Drink canteen
* Jump through window
* Use explosives
* Use wirecutters
2026-01-23 23:19:07 +02:00
Asdow 41945842d5 Migrate features from +AI
Create Scuba fins when in deep water for elites
2026-01-23 18:06:26 +02:00
Asdow 91a1059f87 Migrate features from +AI
Possibly wear gasmask if soldier sees tear or mustard gas
2026-01-23 16:47:34 +02:00
Asdow 9c0fb0b6da Add tactical range defines 2026-01-22 00:32:41 +02:00
Asdow 3d7d7cc88a Reorganize stuff to improve readability 2026-01-22 00:31:31 +02:00
Asdow 737cd848ce Make water and gas checks const 2026-01-22 00:28:37 +02:00
Asdow 1bead3a0fe Improve readability 2026-01-22 00:27:53 +02:00
Asdow b50ed93f39 Remove armed vehicle check from soldier AI 2026-01-22 00:27:18 +02:00
Asdow 75355f6cac Set DangerousSpot to const 2026-01-22 00:26:48 +02:00
Asdow 2448ccb191 Tweak AI screen messages 2026-01-22 00:26:18 +02:00
Asdow 42215e7a51 Remove magic numbers 2026-01-22 00:25:33 +02:00
Asdow 1419cc6925 Avoid overcrowding when looking for spot to move to 2026-01-22 00:24:53 +02:00
Asdow 76af6b7641 Remove duplicate include 2026-01-22 00:23:52 +02:00
Asdow 2b1838dbb0 Remove unused local variable 2026-01-19 23:34:19 +02:00
Asdow 4ad34f80a9 Fix includes 2026-01-19 22:59:55 +02:00
Asdow ff96d653e2 Add skeleton for utilityAI framework 2026-01-19 22:59:30 +02:00
Asdow 58ea3be2fd Remove MercPtrs[]
SoldierID works without it thanks to operator overload
2026-01-19 22:58:58 +02:00
Asdow 4678cab16f Use correct type 2026-01-19 22:58:33 +02:00
Asdow 1b9cb1f75e Add functions to check if soldier is in specific gas 2026-01-19 22:58:17 +02:00
Asdow df8d4bd8a2 Only show message if temperature has changed
This was absolutely clogging Debug build when compressing time
2026-01-19 22:57:34 +02:00
Asdow 52a4959dfb Add optional InOut parameter to ClosestKnownOpponent
distanceInCellCoords will return the distance between pSoldier and closest known opponent. We're often calculating same range after the call using PythSpacesAway and this way it's no longer necessary in most cases.
2026-01-19 22:56:18 +02:00
Asdow e806fd37e7 Add screen messages that display AI decisions 2026-01-19 22:51:38 +02:00
Asdow cd054b549f Split DecideAction into several specialized DecideAction functions
* Removes the need to constantly check for fCivilian, ARMED_VEHICLE, ENEMY_ROBOT etc inside DecideAction
* Move AI decision checks into their own functions, such as DecideActionRadioRedAlert
* Add new entry to ActionType. AI_ACTION_INVALID signals that no valid action was found when a function returns, or no valid action is possible
* Do a weighted random selection for choosing target under DecideActionBlackSoldier
2026-01-19 22:50:27 +02:00
Asdow c3f4ee754a Only log during turn based AI 2026-01-19 22:32:01 +02:00
Asdow a46d2dbb96 Add global to selectively log AI status BLACK decisions 2026-01-19 22:31:34 +02:00
Asdow 16fc2d8177 Cleanup FindBestNearbyCover
* Add optional parameter to ignore search range
2026-01-19 22:29:06 +02:00
Asdow e634edd862 Rename debug draw modes 2026-01-19 22:05:31 +02:00
Asdow f5b705cbe4 Add ingame option to use old AI instead of new 2026-01-19 22:05:10 +02:00
Asdow 4e19816b57 Fix typo 2026-01-19 00:28:19 +02:00
Asdow 20dadec54c Merge branch 'master' into decideaction 2026-01-13 00:38:40 +02:00
Asdow 2e3e4b38d9 Fix compilation 2025-12-25 20:17:02 +02:00
Asdow f075963bca Merge branch 'master' into decideaction 2025-12-25 18:58:41 +02:00
Asdow de91c7a9fd Render debug info in tactical
Functionality for rendering pathfinding and cover values in tactical was behind defunct #ifdefs.
It's now compiled always, but only enabled if cheat level is BEDUG_CHEAT_LEVEL.
Unified rendering the debug info into one function, instead of several nearly identical ones.
Ctrl + Shift + Z cycles through the different debug modes.

Now it should be fairly easy to add more info modes like these two in the future.
2024-02-21 23:24:14 +02:00
Asdow ab2c5fc10d Add BOXER() & ISVIP() macros 2024-02-18 12:03:43 +02:00
AsdowandGitHub 5fe730b075 Merge branch 'master' into decideaction 2024-02-11 17:38:27 +02:00
Asdow 57716e1c07 Revert back to original AI deadlock breaking
For now..
2024-02-11 17:20:10 +02:00
Asdow dfc17c68c6 Cancel action if new situation arises
Lot of actions are liable to deadlock if this is not done
2024-02-11 13:32:17 +02:00
Asdow 67a26e0936 Add more AI logging 2024-02-11 11:21:41 +02:00
Asdow 1a98420160 Remove extraneous check
Allow at least considering throwing smoke for cover even if pSoldier has used some AP from its initial amount.
2024-02-10 18:56:11 +02:00
Asdow 877ec385ee Whitespace changes 2024-02-10 18:50:30 +02:00
Asdow b480a67d36 Reduce code duplication 2024-02-10 18:47:03 +02:00
Asdow adf86380ca Merge branch 'decideaction' of https://github.com/1dot13/source into decideaction 2024-02-10 18:37:11 +02:00
Asdow 93acd19c11 Try to find a cover spot when advancing to attack
Calling InternalGoAsFarAsPossibleTowards() with target's gridno means AI doesn't take into account eg. nearby friendlies resulting in them bunching up and  making a beeline straight at whoever they're trying to attack.
We'll first at least attempt to find a cover spot near said target, if it doesn't work, only then head straight at them.
2024-02-10 18:35:45 +02:00
Asdow f6052f0eff add another AI logging entry 2024-02-10 18:35:25 +02:00
Asdow eaf76957c0 Use correct string array 2024-02-10 18:35:25 +02:00
Asdow 3c2b048367 Attempt to continue moving towards enemy
when in water. If our current or last action was AI_ACTION_SEEK_OPPONENT.

The AI tends to flipflop between jumping into water, swimming a certain distance and then "panic" because they're in the water and then try to reach the closest land tile. Which is often right where they entered the water. Adding a bit of decision momentum with this change should help with that.
2024-02-10 18:35:25 +02:00
Asdow 1aec54f59c add another AI logging entry 2024-02-10 18:14:35 +02:00
Asdow 0240729c11 Use correct string array 2024-02-10 18:13:20 +02:00
Asdow 5c86cf0b07 Attempt to continue moving towards enemy
when in water. If our current or last action was AI_ACTION_SEEK_OPPONENT.

The AI tends to flipflop between jumping into water, swimming a certain distance and then "panic" because they're in the water and then try to reach the closest land tile. Which is often right where they entered the water. Adding a bit of decision momentum with this change should help with that.
2024-02-10 18:12:30 +02:00
Asdow 79372d5d23 Try to find a cover spot when advancing to attack
Calling InternalGoAsFarAsPossibleTowards() with target's gridno means AI doesn't take into account eg. nearby friendlies resulting in them bunching up and  making a beeline straight at whoever they're trying to attack.
We'll first at least attempt to find a cover spot near said target, if it doesn't work, only then head straight at them.
2024-02-04 21:05:54 +02:00
Asdow 4faccb2957 Fix indentation 2024-02-04 20:17:09 +02:00
Asdow 0ed68b6214 Set functions to static 2024-02-04 16:17:36 +02:00
Asdow f646bc2bfa Set correct next action if npc has no AP
With aiData.bAction = AI_ACTION_NONE and aiData.bNextAction = AI_ACTION_END_TURN, the npc turn is correctly ended in the next call to TurnBasedHandleNPCAI()
2024-02-04 16:14:01 +02:00
Asdow 01327265a3 Check if action should stay inprogress state
Several actions tended to get stuck in an invalid state, where it was completed, but aiData.bActionInProgress was still TRUE resulting in an AI deadlock.

If such a situation is found, Actiondone is now called, preventing the deadlock
2024-02-04 16:10:26 +02:00
Asdow cd193c7f5e Call TurnBasedHandleNPCAI() if no action is in progress
Several actions would sometimes get stuck in a forever loop, where they would not be executed, since no proper path for executing the actions is found.

Originally TurnBasedHandleNPCAI() would only be called if aidata.bAction was AI_ACTION_NONE.
Now we'll call it if an action is not already in progress, and will be executed if the action is affordable.
2024-02-04 16:07:03 +02:00
Asdow f249406ba0 Allow specific alert status AI logging
Less clutter in the AI logs if, for example one is only interested in BLACK status decisions
2024-02-04 15:29:05 +02:00
Asdow 7ee8165e02 Prevent AI deadlocking if NEW_SITUATION is encountered 2024-02-04 15:18:43 +02:00
Asdow caf5828153 Remove deadlock breaking from TurnBasedHandleNPCAI
It's already handled before a call to this function, and TurnBasedHandleNPCAI() is only called when pSoldier's action is AI_ACTION_NONE
2024-02-04 12:39:57 +02:00
Asdow ffb95a2418 Remove unused variable from if checks
aiData.bRTPCombat is never initialized and is always 0 when valid values should be

#define RTP_COMBAT_AGGRESSIVE		1
#define RTP_COMBAT_CONSERVE			2
#define RTP_COMBAT_REFRAIN			3
2024-02-03 20:49:43 +02:00
Asdow 22f2c9ff9a Correct AP check
We can't do anything with negative action points either.
2024-02-02 01:00:12 +02:00
Asdow a90e11619d Merge branch 'master' into decideaction 2023-10-10 23:21:12 +03:00
Asdow d6c9cb20b2 Report soldier ID in DebugMsg when interrupt ends 2023-10-02 22:29:47 +03:00
Asdow 1586e8fd2b Allow possibly taking cover without full APs 2023-10-02 22:28:56 +03:00
Asdow 88af94eaaf Use Flugente's AI deadlock break & fix wstring for screenMsg 2023-10-02 22:26:30 +03:00
Asdow 6131ebc689 Comment out temp. fixes to flush out AI deadlocks 2023-10-02 22:20:43 +03:00
Asdow ae709f2d19 Merge branch 'master' into decideaction 2023-10-02 21:19:44 +03:00
Asdow a45f3ae319 Add more AI logging 2023-08-20 23:38:56 +03:00
Asdow 318e4a40cf Improve AI logging 2023-08-19 23:23:59 +03:00
Asdow 1170f82159 Move variable declarations to point of initialization 2023-08-19 21:47:31 +03:00
Asdow d28ed97dff Add section comments do DecideActionRed 2023-08-19 21:45:01 +03:00
Asdow 96a7fa0538 Add section comments to DecideActionBlack 2023-08-19 14:41:16 +03:00
Asdow c5d1615035 Move decision if stuck in water or gas into its own function 2023-08-19 14:39:33 +03:00
Asdow 138ad0656f Prevent AI deadlocks
Canceling current AI actions at the start of soldier's turn prevents a lot of ai deadlocks. The status RED & BLACK actions are supposed re-evaluate the situation every turn anyways
2023-08-19 14:37:49 +03:00
Asdow a0cbe42a01 Create function for decision to wear a gasmask 2023-08-18 23:56:40 +03:00
Asdow 06478266d0 Moved armed vehicle/robot in gas check into function
Originally placed in DecideAction(green/yellow/red/black) functions separately
2023-08-18 23:11:39 +03:00
Asdow 613de1b869 Remove useless code
!AimingGun(pSoldier) would always evaluate to true and there are no hits to CrowDecideAction in the entire solution even in commented out code
2023-08-18 21:05:11 +03:00
Asdow 1c4929c3e1 Improve AI item handling log entry 2023-08-17 19:52:21 +03:00
Asdow 664142c36c Don't log AI info if pSoldier is null 2023-08-17 19:50:59 +03:00
Asdow 1c8d008a66 Move DebugAI() calls inside if blocks
Clutters logs and slows down game less when logging is on.
2023-08-17 19:49:55 +03:00
Asdow 0e529070c4 Allow AI to shoot with lower aim if out of AP for current aim level 2023-08-17 19:42:43 +03:00
Asdow bacad9b4c0 Prevent AI deadlock when they try to shoot and player gets an interrupt 2023-08-17 19:41:41 +03:00
Asdow e502727828 Disable canceling AI actions for escorted mercs
Logging AI decisions shows that this conditional would constantly cancel AI actions during regular gameplay. A special case like this should not affect normal AI routines and I'd rather disable this for now to prevent it from masking other problems with AI decisions.
2023-08-17 19:41:08 +03:00
Asdow d99b12cb5f Allow status black cover advance with full AP 2023-08-16 23:08:03 +03:00
Asdow 070867d965 Check for closest known opponent in status black 2023-08-16 23:07:29 +03:00
93 changed files with 16541 additions and 2544 deletions
+2 -2
View File
@@ -27,7 +27,7 @@ if(ADDRESS_SANITIZER)
endif()
if(MSVC)
add_compile_options("/wd4838") # silence implicit narrowing conversion warnings
add_compile_options("/wd4838")
endif()
# whether we are using MSBuild as a generator
@@ -163,5 +163,5 @@ foreach(app IN LISTS ApplicationTargets)
# for SGP only
target_link_libraries(${app}_sgp PRIVATE "ddraw.lib" "${PROJECT_SOURCE_DIR}/fmodvc.lib")
target_link_libraries(${app}_sgp PRIVATE libpng)
target_compile_definitions(${app}_sgp PRIVATE ${compilationFlags} ${debugFlags} NO_ZLIB_COMPRESSION)
target_compile_definitions(${app}_sgp PRIVATE NO_ZLIB_COMPRESSION)
endforeach()
+3 -1
View File
@@ -332,6 +332,7 @@ BOOLEAN LoadGameSettings()
gGameSettings.fOptions[TOPTION_ALT_START_AIM] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_ALT_START_AIM" , TRUE); // Start at max aiming level instead of default no aiming
gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_ALT_PATHFINDING" , FALSE); // A* pathfinding
gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] = iniReader.ReadBoolean("JA2 Game Settings", "TOPTION_USE_LEGACY_TACTICALAI", FALSE); // A* pathfinding
gGameSettings.fOptions[TOPTION_MERCENARY_FORMATIONS] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_MERCENARY_FORMATIONS" , FALSE ); // Flugente: mercenary formations
gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_SHOW_ENEMY_LOCATION" , FALSE); // sevenfm: show locations of known enemies
gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN] = iniReader.ReadBoolean("JA2 Game Settings","TOPTION_REPORT_MISS_MARGIN" , FALSE ); // HEADROCK HAM 4: Shot offset report
@@ -616,6 +617,7 @@ BOOLEAN SaveGameSettings()
settings << "TOPTION_SHOW_ENEMY_LOCATION = " << (gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] ? "TRUE" : "FALSE" ) << endl;
settings << "TOPTION_ALT_START_AIM = " << (gGameSettings.fOptions[TOPTION_ALT_START_AIM] ? "TRUE" : "FALSE") << endl;
settings << "TOPTION_ALT_PATHFINDING = " << (gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] ? "TRUE" : "FALSE") << endl;
settings << "TOPTION_USE_LEGACY_TACTICALAI = " << (gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] ? "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;
@@ -846,6 +848,7 @@ void InitGameSettings()
gGameSettings.fOptions[TOPTION_SHOW_ENEMY_LOCATION] = FALSE; // sevenfm: show locations of known enemies
gGameSettings.fOptions[TOPTION_ALT_START_AIM] = TRUE;
gGameSettings.fOptions[TOPTION_ALT_PATHFINDING] = FALSE;
gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] = FALSE;
// arynn: Cheat/Debug Menu
gGameSettings.fOptions[ TOPTION_CHEAT_MODE_OPTIONS_HEADER ] = FALSE;
@@ -2749,7 +2752,6 @@ void LoadSkillTraitsExternalSettings()
gSkillTraitValues.ubTERepairRobotPenaltyReduction = iniReader.ReadInteger("Technician","REPAIR_SPEED_ROBOT_PENALTY_REDUCTION", 30, 0, 100);
if (gSkillTraitValues.ubTETraitsNumToRepairRobot == 2) {gSkillTraitValues.ubTERepairRobotPenaltyReduction /= 2; }
gSkillTraitValues.fTETraitsCanRestoreItemThreshold = iniReader.ReadBoolean( "Technician", "MERCS_CAN_DO_ADVANCED_REPAIRS", FALSE );
gSkillTraitValues.ubTechLevelNeededForAdvancedRepair = iniReader.ReadInteger("Technician", "TECH_LEVEL_NEEDED_FOR_ADVANCED_REPAIR", 2, 1, 2);
// DOCTOR
gSkillTraitValues.ubDONumberTraitsNeededForSurgery = iniReader.ReadInteger("Doctor","NUMBER_OF_TRAITS_NEEDED_FOR_SURGERY", 1, 0, 2);
+1 -1
View File
@@ -105,6 +105,7 @@ enum
TOPTION_SHOW_ENEMY_LOCATION,
TOPTION_ALT_START_AIM,
TOPTION_ALT_PATHFINDING,
TOPTION_USE_LEGACY_TACTICALAI,
// arynn: Debug/Cheat
TOPTION_CHEAT_MODE_OPTIONS_HEADER,
@@ -2145,7 +2146,6 @@ typedef struct
UINT8 ubTERepairRobotPenaltyReduction;
UINT8 ubTETraitsNumToRepairRobot;
BOOLEAN fTETraitsCanRestoreItemThreshold;
UINT8 ubTechLevelNeededForAdvancedRepair; // if we have a high enough technician level, we can repair items above the normal threshold (1 for technician, 2 for engineer)
// DOCTOR
UINT8 ubDONumberTraitsNeededForSurgery;
+1 -1
View File
@@ -1822,7 +1822,7 @@ void HandleHighLightedText( BOOLEAN fHighLight )
bLastRegion = -1;
}
if( bHighLight != -1 && toggle_box_array[bHighLight] != -1)
if( bHighLight != -1 )
{
if( bHighLight < OPT_FIRST_COLUMN_TOGGLE_CUT_OFF )
{
-26
View File
@@ -1560,8 +1560,6 @@ BOOLEAN DisplayMercsInventory(UINT8 ubMercID)
CHAR16 gzItemName[ 5000 ];
UINT8 ubItemCount=0;
UINT8 ubColumnCount=0;
const bool realisticGameStyle = gGameOptions.ubGameStyle != STYLE_SCIFI;
const bool reducedGuns = gGameOptions.fGunNut == false;
//if the mercs inventory has already been purchased, dont display the inventory
if ( (gMercProfiles[ ubMercID ].ubMiscFlags & PROFILE_MISC_FLAG_ALREADY_USED_ITEMS) && !gGameExternalOptions.fGearKitsAlwaysAvailable )
@@ -1585,18 +1583,6 @@ BOOLEAN DisplayMercsInventory(UINT8 ubMercID)
{
usItem = gMercProfiles[ubMercID].inv[ i ];
if (realisticGameStyle && ItemIsOnlyInScifi(usItem))
{
gMercProfiles[ubMercID].inv[i] = 0;
continue;
}
if (reducedGuns && ItemIsOnlyInTonsOfGuns(usItem))
{
gMercProfiles[ubMercID].inv[i] = 0;
continue;
}
//if its a valid item AND we are only displaying less then 8 items
if( usItem && ubItemCount < WEAPONBOX_TOTAL_ITEMS )
{
@@ -1676,18 +1662,6 @@ BOOLEAN DisplayMercsInventory(UINT8 ubMercID)
{
usItem = gMercProfiles[ubMercID].inv[ i ];
if (realisticGameStyle && ItemIsOnlyInScifi(usItem))
{
gMercProfiles[ubMercID].inv[i] = 0;
continue;
}
if (reducedGuns && ItemIsOnlyInTonsOfGuns(usItem))
{
gMercProfiles[ubMercID].inv[i] = 0;
continue;
}
//if its a valid item AND we are only displaying less then 8 items
if( usItem && ubItemCount < WEAPONBOX_NUMBER )
{
+4
View File
@@ -6,6 +6,10 @@ set(ModularizedTacticalAISrc
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyAIPlanFactory.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyCreaturePlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/LegacyZombiePlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/BoxerPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/CivilianPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/RobotPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/SoldierPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlan.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/NullPlanFactory.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/src/Plan.cpp"
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around boxer related AI uplifted from original DecideAction() routines
*/
class LegacyAIBoxerPlan: public Plan
{
private:
public:
LegacyAIBoxerPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const {return false;}
};
}
}
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around civilian/noncombatant related AI uplifted from original DecideAction() routines
*/
class CivilianPlan : public Plan
{
private:
public:
CivilianPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const { return false; }
};
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around robot related AI uplifted from original DecideAction() routines
*/
class RobotPlan : public Plan
{
private:
public:
RobotPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const { return false; }
};
}
}
@@ -0,0 +1,23 @@
#pragma once
#include "Plan.h"
namespace AI
{
namespace tactical
{
/**@class LegacyAIPlan
* @brief Component/Concrete Product. Wrapper/Re-Write of DecideAction()
*
* Wrapper around soldier related AI uplifted from original DecideAction() routines
*/
class SoldierPlan : public Plan
{
private:
public:
SoldierPlan(SOLDIERTYPE* npc);
virtual void execute(PlanInputData& environment);
virtual bool done() const { return false; }
};
}
}
+35
View File
@@ -0,0 +1,35 @@
#include "../include/BoxerPlan.h"
#include "../../TacticalAI/ai.h"
namespace AI
{
namespace tactical
{
LegacyAIBoxerPlan::LegacyAIBoxerPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void LegacyAIBoxerPlan::execute(PlanInputData& environment)
{
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenBoxer(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionGreenBoxer(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionBlackBoxer(get_npc());
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlackBoxer(get_npc());
break;
}
}
} // namespace tactical
} // namespace AI
@@ -0,0 +1,51 @@
#include "../include/CivilianPlan.h"
#include "../../TacticalAI/ai.h"
#include "NPC.h"
#include "Soldier Profile.h"
namespace AI
{
namespace tactical
{
CivilianPlan::CivilianPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void CivilianPlan::execute(PlanInputData& environment)
{
if ( !environment.turn_based() )
{
if ( (get_npc()->ubProfile != NO_PROFILE) && (gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL) )
{
TriggerNPCWithGivenApproach(get_npc()->ubProfile, APPROACH_DONE_TRAVERSAL, FALSE);
gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 &= (~PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL);
get_npc()->ubQuoteActionID = 0;
// wait a tiny bit
get_npc()->aiData.usActionData = 100;
get_npc()->aiData.bAction = AI_ACTION_WAIT;
return;
}
}
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenCivilian(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellowCivilian(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRedCivilian(get_npc());
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlackCivilian(get_npc());
break;
}
}
} // namespace tactical
} // namespace AI
@@ -7,6 +7,10 @@
#include "../include/LegacyCreaturePlan.h"
#include "../include/LegacyZombiePlan.h"
#include "../include/LegacyArmedVehiclePlan.h"
#include "../include/BoxerPlan.h"
#include "../include/CivilianPlan.h"
#include "../include/RobotPlan.h"
#include "../include/SoldierPlan.h"
#include "../include/CrowPlan.h"
#include "../include/PlanList.h"
@@ -14,8 +18,7 @@
#include "../../Tactical/Soldier Control.h" // For SOLDIERTYPE definition
#include "../../Tactical/Animation Data.h" // For the definition of, wait for it... BLOODCAT!
#include "Soldier macros.h"
#include <stdio.h>
#include "ai.h"
namespace AI
@@ -43,6 +46,11 @@ namespace AI
if(npc->IsZombie())
return new LegacyZombiePlan(npc);
if (BOXER(npc)) { return new LegacyAIBoxerPlan(npc); }
if (IS_CIV_BODY_TYPE(npc)) { return new CivilianPlan(npc); }
if (ENEMYROBOT(npc)) { return new RobotPlan(npc); }
if (SoldierAI(npc)) { return new SoldierPlan(npc); }
return new LegacyAIPlan(npc); // no special plan for other cases yet, return default legacy AI wrapper
}
+34
View File
@@ -0,0 +1,34 @@
#include "../include/RobotPlan.h"
#include "../../TacticalAI/ai.h"
namespace AI
{
namespace tactical
{
RobotPlan::RobotPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void RobotPlan::execute(PlanInputData& environment)
{
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenRobot(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellowRobot(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRedRobot(get_npc());
break;
case STATUS_BLACK:
get_npc()->aiData.bAction = DecideActionBlackRobot(get_npc());
break;
}
}
} // namespace tactical
} // namespace AI
+104
View File
@@ -0,0 +1,104 @@
#include "../include/SoldierPlan.h"
#include "../../TacticalAI/ai.h"
#include "../../TacticalAI/AIInternals.h" // ACTING_ON_SCHEDULE
#include "../../TacticalAI/NPC.h" // NPCReachedDestination
#include "../../Tactical/Dialogue Control.h" // DialogueQueueIsEmpty
#include "../../Utils/Font Control.h" // ScreenMsg about deadlock
#include "../../i18n/include/Text.h" // Sniper warning
#include "../../Utils/message.h" // ditto
namespace AI
{
namespace tactical
{
SoldierPlan::SoldierPlan(SOLDIERTYPE* npc)
: Plan(npc)
{
}
void SoldierPlan::execute(PlanInputData& environment)
{
if (!environment.turn_based())
{
if ((get_npc()->ubProfile != NO_PROFILE) && (gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 & PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL))
{
TriggerNPCWithGivenApproach(get_npc()->ubProfile, APPROACH_DONE_TRAVERSAL, FALSE);
gMercProfiles[get_npc()->ubProfile].ubMiscFlags3 &= (~PROFILE_MISC_FLAG3_HANDLE_DONE_TRAVERSAL);
get_npc()->ubQuoteActionID = 0;
// wait a tiny bit
get_npc()->aiData.usActionData = 100;
get_npc()->aiData.bAction = AI_ACTION_WAIT;
return;
}
if (get_npc()->bTeam == gbPlayerNum)
{
if (environment.get_tactical_status().fAutoBandageMode)
{
get_npc()->aiData.bAction = DecideAutoBandage(get_npc());
return;
}
}
}
if (get_npc()->bTeam != MILITIA_TEAM)
{
if (!sniperwarning && get_npc()->aiData.bOrders == SNIPER)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_WATHCHOUTFORSNIPERS]);
sniperwarning = TRUE;
// Flugente: additional dialogue
AdditionalTacticalCharacterDialogue_AllInCurrentSector(NO_PROFILE, ADE_SNIPERWARNING);
}
if (!biggunwarning && FindRocketLauncherOrCannon(get_npc()) != NO_SLOT)
{
biggunwarning = TRUE;
//TODO: don't say this again after reloading a savegame
SayQuoteFromAnyBodyInSector(QUOTE_WEARY_SLASH_SUSPUCIOUS);
}
}
get_npc()->aiData.fAIFlags &= (~AI_CAUTIOUS); // turn off cautious flag
// if status override is set, bypass RED/YELLOW and go directly to GREEN!
if ((get_npc()->aiData.bBypassToGreen) && (get_npc()->aiData.bAlertStatus < STATUS_BLACK))
{
get_npc()->aiData.bAction = DecideActionGreenSoldier(get_npc());
if (!gfTurnBasedAI)
{
// reset bypass now
get_npc()->aiData.bBypassToGreen = 0;
}
}
else
{
switch (get_npc()->aiData.bAlertStatus)
{
case STATUS_GREEN:
get_npc()->aiData.bAction = DecideActionGreenSoldier(get_npc());
break;
case STATUS_YELLOW:
get_npc()->aiData.bAction = DecideActionYellowSoldier(get_npc());
break;
case STATUS_RED:
get_npc()->aiData.bAction = DecideActionRedSoldier(get_npc());
break;
case STATUS_BLACK:
//if ( gGameSettings.fOptions[TOPTION_USE_LEGACY_TACTICALAI] ) // Commented out for now since new AI is WIP
{
get_npc()->aiData.bAction = DecideActionBlackSoldier(get_npc());
}
//else
//{
// get_npc()->aiData.bAction = DecideActionBlackSoldierUtilityAI(get_npc());
//}
break;
}
}
DEBUGAIMSG("Deciding for guynum " << (int)get_npc()->ubID << " at gridno " << get_npc()->sGridNo << ", APs " << get_npc()->bActionPoints <<
", decided action: " << (int)get_npc()->aiData.bAction << ", data " << (int)get_npc()->aiData.usActionData);
}
} // namespace tactical
} // namespace AI
+4 -9
View File
@@ -4948,7 +4948,7 @@ static void CollectRepairableItems(SOLDIERTYPE* pRepairSoldier, SOLDIERTYPE* pSo
// Check for attachments (are there stackable items that can take attachments though?)
UINT8 attachmentIndex = 0;
for (attachmentList::const_iterator iter = (*pObj)[stackIndex]->attachments.begin(); iter != (*pObj)[stackIndex]->attachments.end(); ++iter, ++attachmentIndex) {
if ( iter->exists() && IsItemRepairable(pRepairSoldier, iter->usItem, (*iter)[attachmentIndex]->data.objectStatus, (*iter)[attachmentIndex]->data.sRepairThreshold )) {
if (IsItemRepairable(pRepairSoldier, iter->usItem, (*iter)[attachmentIndex]->data.objectStatus, (*iter)[attachmentIndex]->data.sRepairThreshold )) {
// Send the main item, not the attachment
RepairItem item(pObj, pSoldier, (INVENTORY_SLOT) pocketIndex);
itemsToFix.push(item);
@@ -5081,7 +5081,7 @@ OBJECTTYPE* FindRepairableItemInSpecificPocket(SOLDIERTYPE * pSoldier, OBJECTTYP
// have to check for attachments after...
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
// if it's repairable and NEEDS repairing
if ( iter->exists() && IsItemRepairable( pSoldier, iter->usItem, (*iter)[subObject]->data.objectStatus, (*iter)[subObject]->data.sRepairThreshold ) ) {
if ( IsItemRepairable( pSoldier, iter->usItem, (*iter)[subObject]->data.objectStatus, (*iter)[subObject]->data.sRepairThreshold ) && iter->exists() ) {
return( &(*iter) );
}
}
@@ -5171,12 +5171,8 @@ BOOLEAN RepairObject( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pOwner, OBJECTTYPE *
INT16 threshold = 100;
if ( gGameExternalOptions.fAdvRepairSystem && (Item[pObj->usItem].usItemClass & (IC_WEAPON|IC_ARMOUR)) )
{
if (gSkillTraitValues.fTETraitsCanRestoreItemThreshold && NUM_SKILL_TRAITS(pSoldier, TECHNICIAN_NT) >= gSkillTraitValues.ubTechLevelNeededForAdvancedRepair) // Greysa: added skill check for advanced repair. If we have a high enough technician level, we can repair items above the normal threshold (1 for technician, 2 for engineer)
if ( !gSkillTraitValues.fTETraitsCanRestoreItemThreshold || !HAS_SKILL_TRAIT( pSoldier, TECHNICIAN_NT ) )
{
threshold = 100;
}
else
{
threshold = (*pObj)[ubLoop]->data.sRepairThreshold;
}
}
@@ -11089,8 +11085,7 @@ void DetermineWhichAssignmentMenusCanBeShown( void )
CreateDestroyMouseRegionForFacilityMenu();
CreateDestroyMouseRegionsForFacilityAssignmentMenu();
const auto selectedCharacter = gCharactersList[bSelectedInfoChar].usSolID;
if( selectedCharacter < NOBODY && ( (selectedCharacter->stats.bLife == 0 )||(selectedCharacter->bAssignment == ASSIGNMENT_POW ) ) && ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) ) )
if( ( ( gCharactersList[ bSelectedInfoChar ].usSolID->stats.bLife == 0 )||( gCharactersList[bSelectedInfoChar].usSolID->bAssignment == ASSIGNMENT_POW ) ) && ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) ) )
{
// show basic assignment menu
ShowBox( ghRemoveMercAssignBox );
+5 -13
View File
@@ -2481,12 +2481,6 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
DeleteVideoObjectFromIndex( gpAR->iIndent );
DeleteVideoSurfaceFromIndex( gpAR->iInterfaceBuffer );
// Load necessary mapinfo to place corpses
CHAR8 bFilename[50];
INT32 worldRows, worldCols;
GetMapFileName(gpAR->ubSectorX, gpAR->ubSectorY, 0, bFilename, TRUE, TRUE);
LoadWorldInfoForAutoResolve(bFilename, worldRows, worldCols);
if( fDeleteForGood )
{ //Delete the soldier instances -- done when we are completely finished.
@@ -2525,7 +2519,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
RemoveCharacterFromSquads( gpMercs[ i ].pSoldier );
ChangeSoldiersAssignment( gpMercs[ i ].pSoldier, ASSIGNMENT_DEAD );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpMercs[ i ].pSoldier, RandomGridNoUnloadedSector(worldRows, worldCols), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpMercs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
}
else if( gpAR->ubBattleStatus == BATTLE_SURRENDERED || gpAR->ubBattleStatus == BATTLE_CAPTURED )
{
@@ -2630,8 +2624,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
}
// Flugente: drop sector equipment
const auto gridno = RandomGridNoUnloadedSector(worldRows, worldCols);
gpCivs[ i ].pSoldier->DropSectorEquipment(gridno);
gpCivs[ i ].pSoldier->DropSectorEquipment();
if( fDeleteForGood && gpCivs[ i ].pSoldier->stats.bLife < OKLIFE/2 )
{
@@ -2656,7 +2649,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
UpdateMilitia( militia );
}
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, gridno, ADD_DEAD_SOLDIER_TO_SWEETSPOT );
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpCivs[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
StrategicRemoveMilitiaFromSector( gpCivs[ i ].pSoldier->sSectorX, gpCivs[ i ].pSoldier->sSectorY, ubCurrentRank, 1 );
if( ProcessLoyalty() )
@@ -2688,8 +2681,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Autoresolve2");
TrackEnemiesKilled( ENEMY_KILLED_IN_AUTO_RESOLVE, gpEnemies[ i ].pSoldier->ubSoldierClass ); //add casualty to some statistic
if( ProcessLoyalty() )HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ENEMY_KILLED, gpAR->ubSectorX, gpAR->ubSectorY, 0 );
ProcessQueenCmdImplicationsOfDeath( gpEnemies[ i ].pSoldier );
const auto gridno = RandomGridNoUnloadedSector(worldRows, worldCols);
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, gridno, ADD_DEAD_SOLDIER_TO_SWEETSPOT);
AddDeadSoldierToUnLoadedSector( gpAR->ubSectorX, gpAR->ubSectorY, 0, gpEnemies[ i ].pSoldier, RandomGridNo(), ADD_DEAD_SOLDIER_TO_SWEETSPOT );
}
}
}
@@ -6015,7 +6007,7 @@ void AutoResolveMilitiaDropAndPromote()
}
// Flugente: drop sector equipment
gpCivs[i].pSoldier->DropSectorEquipment( NOWHERE );
gpCivs[i].pSoldier->DropSectorEquipment( );
if ( gpCivs[i].pSoldier->stats.bLife < OKLIFE / 2 )
{
+4
View File
@@ -249,6 +249,10 @@ void AdvanceClock( UINT8 ubWarpCode )
guiDay = ( guiGameClock / NUM_SEC_IN_DAY );
guiHour = ( guiGameClock - ( guiDay * NUM_SEC_IN_DAY ) ) / NUM_SEC_IN_HOUR;
guiMin = ( guiGameClock - ( ( guiDay * NUM_SEC_IN_DAY ) + ( guiHour * NUM_SEC_IN_HOUR ) ) ) / NUM_SEC_IN_MIN;
uiHourLua = guiHour;
uiDayLua = guiDay;
uiMinLua = guiMin;
swprintf( WORLDTIMESTR, L"%s %d, %02d:%02d", gpGameClockString[ STR_GAMECLOCK_DAY_NAME ], guiDay, guiHour, guiMin );
+1 -4
View File
@@ -158,6 +158,7 @@ CHAR16 zString[128];
HourlyDrugUpdate();
RebelCommand::HourlyUpdate();
// WANNE: This check should avoid the resaving of a loaded auto-save game, when entering tactical
BOOLEAN doAutoSave = TRUE;
@@ -168,10 +169,6 @@ CHAR16 zString[128];
if (doAutoSave)
{
// We're also using the doAutoSave check to prevent RebelCommand::DailyUpdate() from running a second time when loading an autosave at 00:00
RebelCommand::HourlyUpdate();
if ( AutoSaveToSlot[1] == FALSE && AutoSaveToSlot[2] == FALSE && AutoSaveToSlot[3] == FALSE && AutoSaveToSlot[4] == FALSE )
AutoSaveToSlot[0] = TRUE;
+3 -23
View File
@@ -859,7 +859,6 @@ static int l_PlayerTeamFull(lua_State* L);
static int l_ProfilesStrategicInsertionData(lua_State* L);
static int l_HealBoxers(lua_State* L);
static int l_ResetBoxers(lua_State* L);
static int l_AddVolunteers(lua_State* L);
@@ -913,6 +912,9 @@ static int l_SetIntelAndQuestMapDataForSector(lua_State* L);
using namespace std;
UINT16 idProfil;
UINT32 uiHourLua;
UINT32 uiDayLua;
UINT32 uiMinLua;
UINT16 PROFILLUA_sSectorX;
UINT16 PROFILLUA_sSectorY;
@@ -1187,7 +1189,6 @@ static void IniFunction(lua_State* L, BOOLEAN bQuests)
lua_register(L, "TacticalCharacterDialogueWithSpecialEvent", l_TacticalCharacterDialogueWithSpecialEvent);
lua_register(L, "SetSalary", l_MercSalary);
lua_register(L, "SetProfileStrategicInsertionData", l_ProfilesStrategicInsertionData);
lua_register(L, "HealBoxers", l_HealBoxers);
//Get merc
lua_register(L, "GetDirection", l_GetDirection); //new
@@ -9314,27 +9315,6 @@ static int l_ResetBoxers(lua_State* L)
return 0;
}
// heal boxers back to max health in case the player stays in sector while the boxers are resting
static int l_HealBoxers(lua_State* L)
{
for (UINT8 i = 0; i < NUM_BOXERS; ++i)
{
// Get breath back
gubBoxerID[i]->bBreath = gubBoxerID[i]->bBreathMax;
gubBoxerID[i]->sBreathRed = 0;
// Get life back
gubBoxerID[i]->stats.bLife = gubBoxerID[i]->stats.bLifeMax;
gubBoxerID[i]->bBleeding = 0;
// erase insta-healable injury
gubBoxerID[i]->iHealableInjury = 0;
DebugQuestInfo(String("Lua: healed gubBoxerID[%d] %d back to full health", i, gubBoxerID[i]));
}
return 0;
}
//Set character to sector Y
static int l_SetCharacterSectorY(lua_State* L)
{
+3
View File
@@ -26,6 +26,9 @@ typedef struct
extern ACTION_ITEM_VALUES ActionItemsValues[500];
extern UINT32 uiHourLua;
extern UINT32 uiDayLua;
extern UINT32 uiMinLua;
extern UINT16 PROFILLUA_sSectorX;
extern UINT16 PROFILLUA_sSectorY;
extern UINT8 PROFILLUA_bSectorZ;
+9 -10
View File
@@ -510,6 +510,15 @@ void IniGlobalGameSetting(lua_State *L)
lua_pushinteger(L, gubBoxingMatchesWon);
lua_setglobal(L, "gubBoxingMatchesWon");
lua_pushinteger(L, uiHourLua);
lua_setglobal(L, "cHour");
lua_pushinteger(L, uiDayLua);
lua_setglobal(L, "cDay");
lua_pushinteger(L, uiMinLua);
lua_setglobal(L, "cMin");
lua_pushinteger(L, gbPlayerNum);
lua_setglobal(L, "gbPlayerNum");
@@ -581,16 +590,6 @@ void IniGlobalGameSetting(lua_State *L)
lua_pushinteger(L, guiMin);
lua_setglobal(L, "guiMin");
lua_pushinteger(L, guiDay);
lua_setglobal(L, "cDay");
lua_pushinteger(L, guiHour);
lua_setglobal(L, "cHour");
lua_pushinteger(L, guiMin);
lua_setglobal(L, "cMin");
lua_pushinteger(L, guiCurrentScreen);
lua_setglobal(L, "guiCurrentScreen");
@@ -6024,7 +6024,10 @@ void HandleItemCooldownFunctions( OBJECTTYPE* itemStack, INT32 deltaSeconds, BOO
FLOAT newguntemperature = max(0.0f, guntemperature - tickspassed * cooldownfactor ); // ... calculate new temperature ...
#if JA2TESTVERSION
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", guntemperature, newguntemperature);
if (guntemperature != newguntemperature)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", guntemperature, newguntemperature);
}
#endif
(*itemStack)[i]->data.bTemperature = newguntemperature; // ... set new temperature
@@ -6044,7 +6047,10 @@ void HandleItemCooldownFunctions( OBJECTTYPE* itemStack, INT32 deltaSeconds, BOO
(*iter)[i]->data.bTemperature = newtemperature; // ... set new temperature
#if JA2TESTVERSION
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", temperature, newtemperature);
if (temperature != newtemperature)
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"World: Item temperature lowered from %4.2f to %4.2f", temperature, newtemperature);
}
#endif
// we assume that there can exist only 1 underbarrel weapon per gun
+3 -3
View File
@@ -4881,14 +4881,14 @@ void ExecuteStrategicAIAction( UINT16 usActionCode, INT16 sSectorX, INT16 sSecto
}
// depending on which cities the player currently holds, we send out attack on multiple cities. We try to make these attacks occur simultaneously, so the player will have to fend off
// multiple gigantic attacks on different cities. Ideally, the attacks will be timed so well that the player cannot use a squad in both battles, even with use of the helicopter
// multiple gigantic attacks on different cities. Ideally, the attacks will be timed so well that the player cannot use a sqaud in both battles, even with use of the helicopter
// we first have to check which cities we have to attack. For that, we simply check wether there are troops in the target sector. If not, we will attack here
BOOLEAN fAttack_Grumm = !(SectorInfo[ SEC_H3 ].ubNumTroops > 0);
BOOLEAN fAttack_Cambria = !(SectorInfo[ SEC_H8 ].ubNumTroops > 0);
BOOLEAN fCambriaSAMOccupied = !(SectorInfo[ SEC_I8 ].ubNumTroops > 0);
BOOLEAN fAttack_Alma = !(SectorInfo[ SEC_I13 ].ubNumTroops > 0);
BOOLEAN fAttack_Alma = !(SectorInfo[ SEC_I13 ].ubNumTroops > 0);
BOOLEAN fAttack_Balime = !(SectorInfo[ SEC_L11 ].ubNumTroops > 0);
BOOLEAN fAttack_Chitzena = !(SectorInfo[ SEC_B2 ].ubNumTroops > 0);
BOOLEAN fAttack_Chitzena = !(SectorInfo[ SEC_L11 ].ubNumTroops > 0);
ubNumSoldiers = (UINT8)( gubMinEnemyGroupSize + difficultyMod * 3);
-6
View File
@@ -8472,12 +8472,6 @@ void GetMapKeyboardInput( UINT32 *puiNewEvent )
if ( HandleNailsVestFetish( pSoldier, i, NOTHING ) )
continue;
// tais: never strip Madlab's robot of its installed components (ammo,
// targeting, chassis/armour, utility) - the player can't re-install them
// and the robot would be left defenceless. Only its cargo slot may be emptied.
if ( AM_A_ROBOT( pSoldier ) && i != ROBOT_INVENTORY_SLOT )
continue;
AutoPlaceObjectInInventoryStash(&pSoldier->inv[i], pSoldier->sGridNo, pSoldier->pathing.bLevel);
DeleteObj(&pSoldier->inv[i]);
}
+3 -3
View File
@@ -1423,7 +1423,7 @@ void AddObjectToArmsDealerInventory( UINT8 ubArmsDealer, OBJECTTYPE *pObject )
for (attachmentList::iterator iter = seperateObject[0]->attachments.begin(); iter != seperateObject[0]->attachments.end();) {
// ARM: Note: this is only used for selling, not repairs, so attachmentes are seperated when sold to a dealer
// If the attachment is detachable
if (! (iter->exists() && Item[ iter->usItem ].inseparable))
if (! (Item[ iter->usItem ].inseparable ) && iter->exists())
{
// add this particular attachment (they can't be imprinted, or themselves have attachments!)
AddObjectToArmsDealerInventory( ubArmsDealer, &(*iter) );
@@ -1931,7 +1931,7 @@ UINT32 CalculateObjectItemRepairTime( UINT8 ubArmsDealer, OBJECTTYPE *pItemObjec
// add time to repair any attachments on it
for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) {
// if damaged and repairable
if ( iter->exists() && ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) )
if ( ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) && iter->exists())
{
//uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, iter->usItem, (*iter)[x]->data.objectStatus );
uiRepairTime += CalculateObjectItemRepairTime( ubArmsDealer, &(*iter) );
@@ -1988,7 +1988,7 @@ UINT32 CalculateObjectItemRepairCost( UINT8 ubArmsDealer, OBJECTTYPE *pItemObjec
// add cost of repairing any attachments on it
for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) {
// if damaged and repairable
if ( iter->exists() && ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) )
if ( ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) && iter->exists())
{
uiRepairCost += CalculateObjectItemRepairCost( ubArmsDealer, &(*iter));
}
+8 -10
View File
@@ -21,6 +21,7 @@
#include "GameSettings.h" // added by SANDRO
#include <Font Control.h>
#include <message.h>
#include "Soldier macros.h"
INT32 gsBoxerGridNo[ NUM_BOXERS ] = { 11393, 11233, 11073 };
SoldierID gubBoxerID[ NUM_BOXERS ] = { NOBODY, NOBODY, NOBODY };
@@ -54,7 +55,7 @@ void ExitBoxing( void )
if ( pSoldier != NULL )
{
if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) && InARoom( pSoldier->sGridNo, &usRoom ) && usRoom == BOXING_RING )
if ( BOXER(pSoldier) && InARoom( pSoldier->sGridNo, &usRoom ) && usRoom == BOXING_RING )
{
if ( pSoldier->flags.uiStatusFlags & SOLDIER_PC )
{
@@ -238,7 +239,7 @@ static void CountPeopleInBoxingRingAndDoActions( void )
{
++ubPlayersInRing;
if ( !pNonBoxingPlayer && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) )
if ( !pNonBoxingPlayer && !BOXER(pSoldier) )
{
pNonBoxingPlayer = pSoldier;
}
@@ -290,7 +291,7 @@ static void CountPeopleInBoxingRingAndDoActions( void )
// ladieees and gennleman, we have a fight!
for (uiLoop = 0; uiLoop < 2; ++uiLoop)
{
if (!(pInRing[uiLoop]->flags.uiStatusFlags & SOLDIER_BOXER))
if (!BOXER(pInRing[uiLoop]))
{
// set as boxer!
pInRing[uiLoop]->flags.uiStatusFlags |= SOLDIER_BOXER;
@@ -453,14 +454,11 @@ BOOLEAN BoxerAvailable( void )
return( FALSE );
}
// NOTE THIS IS NOW BROKEN BECAUSE NPC.C ASSUMES THAT BOXERSAVAILABLE < 3 IS A
// SEQUEL FIGHT. Maybe we could check Kingpin's location instead!
UINT8 BoxersAvailable( void )
{
UINT8 ubCount = 0;
// Should also run CheckOnBoxers to make sure boxer IDs are set for this function similarly to BoxerAvailable
if (CheckOnBoxers() == FALSE)
{
return(ubCount);
}
for (UINT8 ubLoop = 0; ubLoop < NUM_BOXERS; ++ubLoop)
{
@@ -514,7 +512,7 @@ void BoxingMovementCheck( SOLDIERTYPE * pSoldier )
// someone moving in/into the ring
CountPeopleInBoxingRingAndDoActions();
}
else if ( ( gTacticalStatus.bBoxingState == BOXING ) && ( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) )
else if ( ( gTacticalStatus.bBoxingState == BOXING ) && BOXER(pSoldier) )
{
// boxer stepped out of the ring!
BoxingPlayerDisqualified( pSoldier, BOXER_OUT_OF_RING );
@@ -564,7 +562,7 @@ void ClearAllBoxerFlags( void )
{
for (UINT32 uiSlot = 0; uiSlot < guiNumMercSlots; ++uiSlot)
{
if ( MercSlots[ uiSlot ] && MercSlots[ uiSlot ]->flags.uiStatusFlags & SOLDIER_BOXER )
if ( MercSlots[ uiSlot ] && BOXER(MercSlots[ uiSlot ]) )
{
// Flugente: nuke the entire opponent count, remove boxing flag, reevaluate opponent list
DecayIndividualOpplist(MercSlots[uiSlot]);
+557 -413
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -758,6 +758,29 @@ void EatFromInventory( SOLDIERTYPE *pSoldier, BOOLEAN fcanteensonly )
}
}
void DrinkFromInventory(SOLDIERTYPE* pSoldier)
{
if ( !pSoldier )
return;
INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsCanteen(pSoldier->inv[bLoop].usItem) )
{
OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]);
if ( pObj && TotalPoints(pObj) > 1 )
{
//ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_DRINKS], pSoldier->GetName());
ApplyConsumable(pSoldier, pObj, false, true);
return;
}
}
}
}
void HourlyFoodUpdate( void )
{
SoldierID bMercID, bLastTeamID;
+1
View File
@@ -108,5 +108,6 @@ void SoldierAutoFillCanteens(SOLDIERTYPE *pSoldier);
BOOLEAN HasFoodInInventory( SOLDIERTYPE *pSoldier, BOOLEAN fCheckFood, BOOLEAN fCheckDrink );
void DrinkFromWaterTap( SOLDIERTYPE *pSoldier );
void DrinkFromInventory(SOLDIERTYPE* pSoldier);
#endif
+51 -2
View File
@@ -73,7 +73,7 @@
#include "TeamTurns.h"
#include "Map Screen Interface.h" // added by Flugente for SquadNames
#include "Keys.h" // added by silversurfer for door handling from the side
#include "Cheats.h"
#include "AIInternals.h"
extern BOOLEAN gubWorldTileInLight[MAX_ALLOWED_WORLD_MAX];
extern BOOLEAN gubIsCorpseThere[MAX_ALLOWED_WORLD_MAX];
@@ -372,7 +372,7 @@ BOOLEAN gfDisplayTimerCursor = FALSE;
UINT32 guiTimerCursorID = 0;
UINT32 guiTimerLastUpdate = 0;
UINT32 guiTimerCursorDelay = 0;
UINT8 gRenderDebugInfoMode = DEBUG_OFF;
CHAR16 gzLocation[ 20 ];
BOOLEAN gfLocation = FALSE;
@@ -500,6 +500,54 @@ void GetMercOknoDirection( SoldierID ubSoldierID, BOOLEAN *pfGoDown, BOOLEAN *pf
}
//----------------------------------------------------------------------------------
void HandleRenderDebugInfoModes()
{
if (DEBUG_CHEAT_LEVEL())
{
switch (gRenderDebugInfoMode)
{
case DEBUG_PATHFINDING:
// Nothing to do here, pathfinding info is filled in the pathing functions.
break;
case DEBUG_THREATVALUE:
break;
case DEBUG_COVERVALUE:
// Calculate cover values for pSoldier under cursor, or for currently selected merc, if nobody is under the cursor.
if (gTacticalStatus.Team[OUR_TEAM].bTeamActive)
{
static SOLDIERTYPE* previousSoldier = nullptr;
static INT32 previousLocation = NOWHERE;
static UINT8 previousStance = 0;
SoldierID usSoldierIndex = NOBODY;
UINT32 uiMercFlags;
FindSoldierFromMouse(&usSoldierIndex, &uiMercFlags);
if (usSoldierIndex == NOBODY)
{
usSoldierIndex = gusSelectedSoldier;
}
if (usSoldierIndex != NOBODY)
{
// Get Soldier
INT32 iPercentBetter;
SOLDIERTYPE* pSoldier;
GetSoldier(&pSoldier, usSoldierIndex);
if (previousSoldier != pSoldier || previousLocation != pSoldier->sGridNo || previousStance != gAnimControl[pSoldier->usAnimState].ubEndHeight)
{
FindBestNearbyCover(pSoldier, pSoldier->aiData.bAIMorale, &iPercentBetter, NOWHERE, false);
previousSoldier = pSoldier;
previousLocation = pSoldier->sGridNo;
previousStance = gAnimControl[pSoldier->usAnimState].ubEndHeight;
}
}
}
break;
default: // off
break;
}
}
}
void PreventFromTheFreezingBug(SOLDIERTYPE* pSoldier)
{
@@ -673,6 +721,7 @@ UINT32 HandleTacticalUI( void )
}
}
HandleRenderDebugInfoModes();
// Check if current event has changed and clear event if so, to prepare it for execution
// Clearing it does things like set first time flag, param variables, etc
if ( uiNewEvent != guiOldEvent )
+1
View File
@@ -184,6 +184,7 @@ extern UINT32 guiCurrentEvent;
extern INT16 gsSelectedLevel;
extern BOOLEAN gfPlotNewMovement;
extern UINT32 guiPendingOverrideEvent;
extern UINT8 gRenderDebugInfoMode;
// GLOBALS
+2 -2
View File
@@ -10073,7 +10073,7 @@ void DrawArmorValues( OBJECTTYPE * gpItemDescObject )
UINT16 iFinalProtectionValue = iProtectionValue;
for (attachmentList::iterator iter = (*gpItemDescObject)[0]->attachments.begin(); iter != (*gpItemDescObject)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && Item[ iter->usItem ].usItemClass == IC_ARMOUR)
if (Item[ iter->usItem ].usItemClass == IC_ARMOUR)
{
iFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection;
}
@@ -10099,7 +10099,7 @@ void DrawArmorValues( OBJECTTYPE * gpItemDescObject )
UINT16 iComparedFinalProtectionValue = iComparedProtectionValue;
for (attachmentList::iterator iter = (*gpComparedItemDescObject)[0]->attachments.begin(); iter != (*gpComparedItemDescObject)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && Item[ iter->usItem ].usItemClass == IC_ARMOUR)
if (Item[ iter->usItem ].usItemClass == IC_ARMOUR)
{
iComparedFinalProtectionValue += Armour[Item[iter->usItem].ubClassIndex].ubProtection;
}
+4 -22
View File
@@ -643,29 +643,13 @@ void popupCallbackItem(INT16 itemId){
i++;p++;
}
static OBJECTTYPE pObjTmp;
OBJECTTYPE pObjTmp;
pObjTmp.initialize();
if( bestStack->RemoveObjectAtIndex(leastDamagedIndex, &pObjTmp) ){
gpItemPointer = &pObjTmp; // pick up the object (or stack)
DoAttachment((UINT8)gubPopupStatusIndex, guiPopupItemPos); // try to attach it
// If attaching failed, change mouse cursor to item as we're still holding it
if ( gpItemPointer->exists() ) {
// Set mouse
guiExternVo = GetInterfaceGraphicForItem(&(Item[gpItemPointer->usItem]));
gusExternVoSubIndex = Item[gpItemPointer->usItem].ubGraphicNum;
MSYS_ChangeRegionCursor(&gMPanelRegion, EXTERN_CURSOR);
MSYS_SetCurrentCursor(EXTERN_CURSOR);
fMapInventoryItem = TRUE;
fTeamPanelDirty = TRUE;
//Dirty interface
fInterfacePanelDirty = DIRTYLEVEL2;
UpdateItemHatches();
}
//gpItemPointer = NULL; // dont drop it!
gItemDescAttachmentPopups[giActiveAttachmentPopup]->hide();
RenderItemDescriptionBox();
@@ -6076,8 +6060,7 @@ void ItemDescAmmoCallback(GUI_BUTTON *btn,INT32 reason)
gfItemAmmoDown = FALSE;
//CHRISL: We dont' want to be able to reload guns using the ammo crate from this function
//Greysa: add check for ammo item
if( gpItemPointer != NULL && ( (Item[gpItemPointer->usItem].usItemClass != IC_AMMO || Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubMagType >= AMMO_BOX) ) || !EnoughPoints(gpItemDescSoldier, APBPConstants[AP_RELOAD_GUN], 0, TRUE) )//dnl ch65 040913
if((gpItemPointer != NULL && Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubMagType >= AMMO_BOX) || !EnoughPoints(gpItemDescSoldier, APBPConstants[AP_RELOAD_GUN], 0, TRUE))//dnl ch65 040913
{
fInterfacePanelDirty = DIRTYLEVEL2;
btn->uiFlags &= (~BUTTON_CLICKED_ON );
@@ -6100,8 +6083,7 @@ void ItemDescAmmoCallback(GUI_BUTTON *btn,INT32 reason)
else
{
//holding an item
//Greysa: add check for ammo item
if( Item[gpItemPointer->usItem].usItemClass == IC_AMMO && Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubCalibre == Weapon[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre )
if(Magazine[Item[gpItemPointer->usItem].ubClassIndex].ubCalibre == Weapon[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre)
{
ReloadGun(gpItemDescSoldier, gpItemDescObject, gpItemPointer, ubStatusIndex);
}
+1 -1
View File
@@ -6265,7 +6265,7 @@ BOOLEAN ShowSoldierRoleSymbol(SOLDIERTYPE* pSoldier)
if ( pSoldier->usSkillCounter[SOLDIER_COUNTER_ROLE_OBSERVED] >= gGameExternalOptions.usTurnsToUncover )
{
// are we a VIP? show that only when the player knows a VIP is in this sector. otherwise, don't even show our officer property
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP && !pSoldier->bSectorZ )
if (ISVIP(pSoldier) && !pSoldier->bSectorZ )
{
if ( PlayerKnowsAboutVIP( pSoldier->sSectorX, pSoldier->sSectorY ) )
{
+104 -137
View File
@@ -1995,7 +1995,7 @@ OBJECTTYPE* FindAttachment( OBJECTTYPE * pObj, UINT16 usItem, UINT8 subObject )
{
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
if ( iter->exists() && iter->usItem == usItem )
if (iter->usItem == usItem && iter->exists())
{
return &(*iter);
}
@@ -2008,7 +2008,7 @@ OBJECTTYPE* FindAttachmentByClass( OBJECTTYPE * pObj, UINT32 uiItemClass, UINT8
{
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == uiItemClass )
if (Item[iter->usItem].usItemClass == uiItemClass && iter->exists())
{
return &(*iter);
}
@@ -2021,7 +2021,7 @@ OBJECTTYPE* FindAttachmentByAttachmentClass( OBJECTTYPE * pObj, UINT32 uiAttachm
{
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[subObject]->attachments.begin(); iter != (*pObj)[subObject]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].attachmentclass == uiAttachmentClass )
if (Item[iter->usItem].attachmentclass == uiAttachmentClass && iter->exists())
{
return &(*iter);
}
@@ -2139,7 +2139,7 @@ OBJECTTYPE* FindNonSmokeLaunchableAttachment( OBJECTTYPE * pObj, UINT16 usWeapon
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ValidLaunchable( iter->usItem, usWeapon) && Explosive[Item[iter->usItem].ubClassIndex].ubType != EXPLOSV_SMOKE )
if (ValidLaunchable( iter->usItem, usWeapon) && Explosive[Item[iter->usItem].ubClassIndex].ubType != EXPLOSV_SMOKE && iter->exists())
{
return &(*iter);
}
@@ -2162,7 +2162,7 @@ BOOLEAN ItemHasAttachments( OBJECTTYPE * pObj, SOLDIERTYPE * pSoldier, UINT8 ite
{
for(attachmentList::iterator att = (*pObj)[iter]->attachments.begin(); att != (*pObj)[iter]->attachments.end(); ++att)
{
if ( att->exists() && att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
if ( att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
{
attachmentHidden = FALSE;
break;
@@ -2178,7 +2178,7 @@ BOOLEAN ItemHasAttachments( OBJECTTYPE * pObj, SOLDIERTYPE * pSoldier, UINT8 ite
{
for(attachmentList::iterator att = (*pObj)[iter]->attachments.begin(); att != (*pObj)[iter]->attachments.end(); ++att)
{
if ( att->exists() && att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
if ( att->usItem != 0 && !ItemIsHiddenAttachment(att->usItem) )
{
attachmentHidden = FALSE;
break;
@@ -3872,111 +3872,13 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
{
OBJECTTYPE *pObj, *pObj2;
INT8 bSlot;
INT16 bAPCost;
INT16 bAPCost;
BOOLEAN fRet = FALSE;
CHECKF( pSoldier );
// Flugente: check for underbarrel weapons and use that object if necessary
pObj = pSoldier->GetUsedWeapon( &(pSoldier->inv[HANDPOS]) );
pObj2 = NULL;
if (pSoldier->IsValidSecondHandShotForReloadingPurposes()) //check for valid second hand weapon for reloading purposes (something that doesn't use ammo)
{
pObj2 = pSoldier->GetUsedWeapon( &(pSoldier->inv[SECONDHANDPOS]) );
}
// Greysa: Check if weapon is jammed and unjam it first
if ((*pObj)[0]->data.gun.bGunAmmoStatus < 0 || ((pObj2 != NULL) && (*pObj2)[0]->data.gun.bGunAmmoStatus < 0))
{
if ((*pObj)[0]->data.gun.bGunAmmoStatus < 0)
{
//borrowed from Weapons.cpp
if (EnoughPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM], FALSE))
{
DeductPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM]);
INT8 bChanceMod;
if (Weapon[pObj->usItem].EasyUnjam)
bChanceMod = 100;
else
bChanceMod = (INT8)(GetReliability(pObj) * 4);
int iResult = SkillCheck(pSoldier, UNJAM_GUN_CHECK, bChanceMod);
if (iResult > 0)
{
// yay! unjammed the gun
(*pObj)[0]->data.gun.bGunAmmoStatus *= -1;
// MECHANICAL/DEXTERITY GAIN: Unjammed a gun
if (bChanceMod < 100) // don't give exp for unjamming an easily unjammable gun
{
StatChange(pSoldier, MECHANAMT, 5, FALSE);
StatChange(pSoldier, DEXTAMT, 5, FALSE);
}
DirtyMercPanelInterface(pSoldier, DIRTYLEVEL2);
PlayJA2Sample(Weapon[Item[pObj->usItem].ubClassIndex].ManualReloadSound, RATE_11025, SoundVolume(HIGHVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo));
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_UNJAMMED], pSoldier->GetName(), ItemNames[pObj->usItem]);
// merc voice feedback?
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_FAILED_UNJAM], pSoldier->GetName(), ItemNames[pObj->usItem]);
}
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_NO_AP_NO_UNJAM], pSoldier->GetName(), ItemNames[pObj->usItem]);
}
}
if ((pObj2 != NULL) && (*pObj2)[0]->data.gun.bGunAmmoStatus < 0)
{
if (EnoughPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM], FALSE))
{
DeductPoints(pSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM]);
INT8 bChanceMod;
if (Weapon[pObj2->usItem].EasyUnjam)
bChanceMod = 100;
else
bChanceMod = (INT8)(GetReliability(pObj2) * 4);
int iResult = SkillCheck(pSoldier, UNJAM_GUN_CHECK, bChanceMod);
if (iResult > 0)
{
// yay! unjammed the gun
(*pObj2)[0]->data.gun.bGunAmmoStatus *= -1;
// MECHANICAL/DEXTERITY GAIN: Unjammed a gun
if (bChanceMod < 100) // don't give exp for unjamming an easily unjammable gun
{
StatChange(pSoldier, MECHANAMT, 5, FALSE);
StatChange(pSoldier, DEXTAMT, 5, FALSE);
}
DirtyMercPanelInterface(pSoldier, DIRTYLEVEL2);
PlayJA2Sample(Weapon[Item[pObj2->usItem].ubClassIndex].ManualReloadSound, RATE_11025, SoundVolume(HIGHVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo));
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_UNJAMMED], pSoldier->GetName(), ItemNames[pObj2->usItem]);
// merc voice feedback?
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_FAILED_UNJAM], pSoldier->GetName(), ItemNames[pObj2->usItem]);
}
}
else
{
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_NO_AP_NO_UNJAM], pSoldier->GetName(), ItemNames[pObj2->usItem]);
}
}
return FALSE; // Greysa: We want to skip reloading if we attempted to unjam, regardless of outcome. Return value doesn't seem to matter as there doesn't seem to be any actual checks on the returned value. I picked FALSE as actual reload hasn't occurred
}
//<SB> manual recharge
if ((*pObj)[0]->data.gun.ubGunShotsLeft && !((*pObj)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
@@ -4013,8 +3915,10 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
PlayJA2Sample( Weapon[ Item[pObj->usItem].ubClassIndex ].ManualReloadSound, RATE_11025, SoundVolume( HIGHVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) );
if (pObj2 != NULL)
if ( pSoldier->IsValidSecondHandShot( ) )
{
pObj2 = &(pSoldier->inv[SECONDHANDPOS]);
if ((*pObj2)[0]->data.gun.ubGunShotsLeft && !((*pObj2)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{
(*pObj2)[0]->data.gun.ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
@@ -4026,8 +3930,10 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
}
else
{
if (pObj2 != NULL)
if ( pSoldier->IsValidSecondHandShot( ) )
{
pObj2 = &(pSoldier->inv[SECONDHANDPOS]);
if ((*pObj2)[0]->data.gun.ubGunShotsLeft && !((*pObj2)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) )
{
(*pObj2)[0]->data.gun.ubGunState |= GS_CARTRIDGE_IN_CHAMBER;
@@ -4083,19 +3989,21 @@ BOOLEAN AutoReload( SOLDIERTYPE * pSoldier, bool aReloadEvenIfNotEmpty )
// if we are valid for two-pistol shooting (reloading) and we have enough APs still
// then do a reload of both guns!
// Flugente: only reload if it's empty, or we really want to
if ( pObj2 != NULL && ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, SECONDHANDPOS ) ) )
if ( pSoldier->IsValidSecondHandShotForReloadingPurposes()
&& ( aReloadEvenIfNotEmpty || !EnoughAmmo( pSoldier, FALSE, SECONDHANDPOS ) ) )
{
// Flugente: check for underbarrel weapons and use that object if necessary
pObj = pSoldier->GetUsedWeapon( &( pSoldier->inv[SECONDHANDPOS] ) );
bSlot = FindAmmoToReload( pSoldier, SECONDHANDPOS, NO_SLOT );
if ( bSlot != NO_SLOT )
{
// ce would reload using this ammo!
bAPCost = GetAPsToReloadGunWithAmmo( pSoldier, pObj2, &( pSoldier->inv[bSlot] ) );
bAPCost = GetAPsToReloadGunWithAmmo( pSoldier, pObj, &( pSoldier->inv[bSlot] ) );
if ( EnoughPoints( pSoldier, (INT16)bAPCost, 0, FALSE ) )
{
// reload the 2nd gun too
fRet = ReloadGun( pSoldier, pObj2, &( pSoldier->inv[bSlot] ) );
fRet = ReloadGun( pSoldier, pObj, &( pSoldier->inv[bSlot] ) );
}
else
{
@@ -5658,7 +5566,7 @@ BOOLEAN OBJECTTYPE::AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttac
//Madd: if the attaching/merging item had any attachments on it, then try to move them to the first result
for (attachmentList::iterator iter = (*pAttachment)[0]->attachments.begin(); iter != (*pAttachment)[0]->attachments.end();) {
if( iter->exists() && ValidItemAttachmentSlot(this, iter->usItem, TRUE, FALSE, subObject )){
if( ValidItemAttachmentSlot(this, iter->usItem, TRUE, FALSE, subObject )){
//This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment);
@@ -5995,7 +5903,7 @@ void RemoveProhibitedAttachments(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//Start by trying to re-attach inseperable items. They take precedence over items that can normally be removed
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) {
if( iter->exists() && Item[iter->usItem].inseparable && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){
if(Item[iter->usItem].inseparable && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){
//This seems to be rather valid. Can't be 100% sure though.
if(pObj->AttachObject(NULL, &(*iter), FALSE, 0, -1, FALSE, usAttachmentSlotIndexVector)){
//Ok now we can be sure, lets remove this object so we don't try to drop it later.
@@ -6009,7 +5917,7 @@ void RemoveProhibitedAttachments(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
}
//Try to attach all the other attachments that didn't fit their current slot.
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) {
if( iter->exists() && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){
if(ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, 0, -1, 0, NULL, usAttachmentSlotIndexVector)){
//This seems to be rather valid. Can't be 100% sure though.
if(pObj->AttachObject(NULL, &(*iter), FALSE, 0, -1, FALSE, usAttachmentSlotIndexVector)){
//Ok now we can be sure, lets remove this object so we don't try to drop it later.
@@ -6185,7 +6093,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//First re-attach any slot-changing attachments.
for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end();) {
if( iter->exists() && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex )){
if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex )){
//This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment);
@@ -6202,7 +6110,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//Time to re-attach the other attachments, if we can. I am the king of copy pasta.
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) {
if( iter->exists() && ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex)){
if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex)){
//This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
CreateItem(iter->usItem, (*iter)[0]->data.objectStatus, &tempAttachment);
@@ -6219,7 +6127,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
//drop all items we couldn't re-attach.
for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].inseparable != 1)
if ( Item[iter->usItem].inseparable != 1)
{//WarmSteel - Couldn't re-attach this item, try to drop it.
if (pSoldier) {
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
@@ -6237,7 +6145,7 @@ attachmentList ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16
}
//and the rest too
for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].inseparable != 1)
if ( Item[iter->usItem].inseparable != 1)
{//WarmSteel - Couldn't re-attach this item, try to drop it.
if (pSoldier) {
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
@@ -10422,7 +10330,7 @@ BOOLEAN NCTHIsScoped( OBJECTTYPE * pObj )
return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].scopemagfactor > 1.0 )
if ( Item[iter->usItem].scopemagfactor > 1.0 && iter->exists() )
return TRUE;
}
}
@@ -10441,7 +10349,7 @@ BOOLEAN IsScoped( OBJECTTYPE * pObj )
return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].aimbonus > 0 )
if ( Item[iter->usItem].aimbonus > 0 && iter->exists())
return TRUE;
}
}
@@ -10528,7 +10436,7 @@ INT16 GetBaseScopeAimBonus( OBJECTTYPE * pObj, INT32 iRange )
//Search for the most powerful scope we can use.
for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); iter++)
{
if( iter->exists() && Item[iter->usItem].aimbonus > bonus && iRange >= Item[iter->usItem].minrangeforaimbonus )
if(Item[iter->usItem].aimbonus > bonus && iRange >= Item[iter->usItem].minrangeforaimbonus && iter->exists())
{
bonus = Item[iter->usItem].aimbonus;
}
@@ -10606,7 +10514,7 @@ UINT32 FindRangeBonusAttachment( OBJECTTYPE * pObj )
{
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].rangebonus > 0 )
if (Item[iter->usItem].rangebonus > 0 && iter->exists())
{
return( Item[iter->usItem].uiIndex );
}
@@ -10627,7 +10535,7 @@ INT16 GetRangeBonus( OBJECTTYPE * pObj )
bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].rangebonus;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ) )
if ( !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ) && iter->exists())
bonus += BonusReduce( Item[iter->usItem].rangebonus, (*iter)[0]->data.objectStatus );
}
}
@@ -12053,7 +11961,7 @@ BOOLEAN HasThermalOptics( SOLDIERTYPE * pSoldier )
OBJECTTYPE* pObj = &pSoldier->inv[HANDPOS];
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ItemIsThermalOptics(iter->usItem) )
if (ItemIsThermalOptics(iter->usItem) && iter->exists() )
return TRUE;
}
}
@@ -12110,7 +12018,7 @@ BOOLEAN IsDuckbill( OBJECTTYPE * pObj )
return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ItemIsDuckbill(iter->usItem) )
if (ItemIsDuckbill(iter->usItem) && iter->exists() )
{
return( TRUE );
}
@@ -12161,7 +12069,7 @@ BOOLEAN IsDetonatorAttached( OBJECTTYPE * pObj )
// return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && IsAttachmentClass( iter->usItem, AC_DETONATOR ) )
if ( IsAttachmentClass( iter->usItem, AC_DETONATOR ) && iter->exists() )
{
return( TRUE );
}
@@ -12177,7 +12085,7 @@ BOOLEAN IsRemoteDetonatorAttached( OBJECTTYPE * pObj )
// return TRUE;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && IsAttachmentClass( iter->usItem, AC_REMOTEDET ) )
if ( IsAttachmentClass( iter->usItem, AC_REMOTEDET ) && iter->exists() )
{
return( TRUE );
}
@@ -12449,7 +12357,7 @@ OBJECTTYPE* FindAttachedBatteries( OBJECTTYPE * pObj )
{
if (pObj->exists() == true) {
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && ItemIsBatteries(iter->usItem) )
if (ItemIsBatteries(iter->usItem) && iter->exists())
{
return( &(*iter) );
}
@@ -12518,6 +12426,51 @@ INT8 FindCamoKit( SOLDIERTYPE * pSoldier )
return( NO_SLOT );
}
INT8 FindCanteen(SOLDIERTYPE* pSoldier)
{
const INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsCanteen(pSoldier->inv[bLoop].usItem) )
{
OBJECTTYPE* pObj = &(pSoldier->inv[bLoop]);
if ( pObj && TotalPoints(pObj) > 1 )
{
return(bLoop);
}
}
}
return(NO_SLOT);
}
INT8 FindWirecutters(SOLDIERTYPE* pSoldier)
{
const INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsWirecutters(pSoldier->inv[bLoop].usItem) )
{
return(bLoop);
}
}
return NO_SLOT;
}
INT8 FindTNT(SOLDIERTYPE* pSoldier)
{
const INT8 invsize = pSoldier->inv.size();
for ( INT8 bLoop = 0; bLoop < invsize; ++bLoop )
{
if ( pSoldier->inv[bLoop].exists() && ItemIsTNT(pSoldier->inv[bLoop].usItem) )
{
return(bLoop);
}
}
return NO_SLOT;
}
//JMich_SkillModifiers: Adding a function to see if we have an item with disarm bonus
INT8 FindDisarmKit( SOLDIERTYPE * pSoldier )
{
@@ -12681,7 +12634,7 @@ INT16 GetCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].camobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].camobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
}
}
@@ -12694,7 +12647,7 @@ INT16 GetUrbanCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].urbanCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].urbanCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
}
}
@@ -12707,7 +12660,7 @@ INT16 GetDesertCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].desertCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].desertCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
}
}
@@ -12720,7 +12673,7 @@ INT16 GetSnowCamoBonus( OBJECTTYPE * pObj )
bonus = (Item[pObj->usItem].snowCamobonus);// * (WEAPON_STATUS_MOD((*pObj)[0]->data.objectStatus) / 100)) ;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsCamoKit(iter->usItem) )
if (!ItemIsCamoKit(iter->usItem) && iter->exists())
bonus += (INT16) (Item[iter->usItem].snowCamobonus);// * (WEAPON_STATUS_MOD((*iter)[0]->data.objectStatus) / 100));
}
}
@@ -13165,7 +13118,7 @@ OBJECTTYPE* FindNightGogglesInInv( SOLDIERTYPE * pSoldier, INT8 * bSlot, BOOLEAN
if (searchAllInventory) {
for (UINT8 loop = 0; loop < pSoldier->inv[bLoop].ubNumberOfObjects; loop ++){
for (attachmentList::iterator iter = pSoldier->inv[bLoop][loop]->attachments.begin(); iter != pSoldier->inv[bLoop][loop]->attachments.end(); ++iter) {
if ( iter->exists() && Item[ iter->usItem ].cavevisionrangebonus > bonusToBeat && Item[ iter->usItem ].usItemClass == IC_FACE ) {
if ( Item[ iter->usItem ].cavevisionrangebonus > bonusToBeat && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists() ) {
pGoggles = &(*iter);
*bSlot = bLoop;
*isAttach = TRUE;
@@ -13223,7 +13176,7 @@ INT16 GetMinRangeForAimBonus( SOLDIERTYPE* pSoldier, OBJECTTYPE * pObj )
attachmentList::iterator iterend = (*pObj)[0]->attachments.end();
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != iterend; ++iter)
{
if ( !gGameExternalOptions.fScopeModes || (iter->exists() && !IsAttachmentClass(iter->usItem, AC_SCOPE|AC_SIGHT|AC_IRONSIGHT )) )
if ( !gGameExternalOptions.fScopeModes || !IsAttachmentClass(iter->usItem, AC_SCOPE|AC_SIGHT|AC_IRONSIGHT ) )
bonus += Item[iter->usItem].minrangeforaimbonus;
}
}
@@ -14141,7 +14094,7 @@ INT16 GetAverageBestLaserRange( OBJECTTYPE * pObj )
}
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && Item[iter->usItem].bestlaserrange > 0 )
if (Item[iter->usItem].bestlaserrange > 0 && iter->exists())
{
numModifiers++;
bonus += (FLOAT) Item[iter->usItem].bestlaserrange;
@@ -14167,7 +14120,7 @@ INT16 GetBestLaserRange( OBJECTTYPE * pObj )
}
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && Item[iter->usItem].bestlaserrange > range )
if (Item[iter->usItem].bestlaserrange > range && iter->exists())
{
range = Item[iter->usItem].bestlaserrange;
}
@@ -15180,7 +15133,7 @@ BOOLEAN OBJECTTYPE::TransformObject( SOLDIERTYPE * pSoldier, UINT8 ubStatusIndex
// I am the prince of copy pasta ;)
for (attachmentList::iterator iter = unattachableList.begin(); iter != unattachableList.end();)
{
if( iter->exists() && ValidItemAttachmentSlot(&gTempObject, iter->usItem, TRUE, FALSE, ubStatusIndex ))
if( ValidItemAttachmentSlot(&gTempObject, iter->usItem, TRUE, FALSE, ubStatusIndex ))
{
//This seems to be rather valid. Can't be 100% sure though.
OBJECTTYPE tempAttachment; // Madd: we must recreate the attachments because they may themselves have default inseparable attachments...
@@ -15868,7 +15821,7 @@ INT32 GetPercentRangeBonus( OBJECTTYPE * pObj )
bonus = ( bonus * ( 100 + Item[(*pObj)[0]->data.gun.usGunAmmoItem].percentrangebonus ) ) / 100;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ))
if ( !ItemIsDuckbill(iter->usItem) || (ItemIsDuckbill(iter->usItem) && (*pObj)[0]->data.gun.ubGunAmmoType == AMMO_BUCKSHOT ))
bonus = ( bonus * ( 100 + BonusReduce( Item[iter->usItem].percentrangebonus, (*iter)[0]->data.objectStatus ) ) ) / 100;
}
}
@@ -16137,6 +16090,20 @@ BOOLEAN FindAttachmentRange(UINT16 usAttachment, UINT32* pStartIndex, UINT32* pE
return result;
}
BOOLEAN ItemIsTNT(UINT16 usItem)
{
if ( Item[usItem].usItemClass == IC_BOMB &&
!ItemIsMine(usItem) &&
!ItemIsTripwire(usItem) &&
Explosive[Item[usItem].ubClassIndex].ubType == EXPLOSV_NORMAL &&
Explosive[Item[usItem].ubClassIndex].ubDamage > 40 &&
GetLauncherFromLaunchable(usItem) == NOTHING )
return TRUE;
return FALSE;
}
////////////////////////////////////
// Item flagmask utility functions
// Just to improve readability
+5
View File
@@ -249,6 +249,7 @@ BOOLEAN ItemIsOnlyInDisease(UINT16 usItem);
BOOLEAN ItemProvidesRobotCamo(UINT16 usItem);
BOOLEAN ItemProvidesRobotNightvision(UINT16 usItem);
BOOLEAN ItemProvidesRobotLaserBonus(UINT16 usItem);
BOOLEAN ItemIsTNT(UINT16 usItem);
//Existing functions without header def's, added them here, just incase I'll need to call
//them from the editor.
@@ -479,9 +480,13 @@ INT8 FindFirstAidKit( SOLDIERTYPE * pSoldier );
INT8 FindDisarmKit( SOLDIERTYPE * pSoldier ); //JMich_SkillsModifiers: Added function to check for disarm bonus
INT8 FindLocksmithKit( SOLDIERTYPE * pSoldier );
INT8 FindCamoKit( SOLDIERTYPE * pSoldier );
INT8 FindCanteen(SOLDIERTYPE* pSoldier);
INT8 FindWalkman( SOLDIERTYPE * pSoldier );
INT8 FindTrigger( SOLDIERTYPE * pSoldier );
INT8 FindRemoteControl( SOLDIERTYPE * pSoldier );
INT8 FindWirecutters(SOLDIERTYPE* pSoldier);
INT8 FindTNT(SOLDIERTYPE* pSoldier);
INT16 GetWornCamo( SOLDIERTYPE * pSoldier );
INT16 GetCamoBonus( OBJECTTYPE * pObj );
INT16 GetWornStealth( SOLDIERTYPE * pSoldier );
+1 -1
View File
@@ -171,7 +171,7 @@ INT32 GetSpreadPattern( OBJECTTYPE * pObj )
//If there are attachments, check them. Stop on the first one with something defined.
//Dear God, I hate C++ iterators. What a fugly mess. //WarmSteel - I made it even messier ;3
for (attachmentList::iterator iter = pObj[0][0]->attachments.begin(); iter != pObj[0][0]->attachments.end(); ++iter){
if( iter->exists() && (n=Item[ iter->usItem ].spreadPattern) ){
if( (n=Item[ iter->usItem ].spreadPattern) && iter->exists()){
//An attachment has it, and it trumps everything, so return it's value.
return n;}
}
+1 -1
View File
@@ -106,7 +106,7 @@ namespace LogicalBodyTypes {
data->filter = FilterDB::Instance().FindFilter(aFilter);
if (data->filter == NULL) throw XMLParseException("Unknown filter specified!", name, data->pParser);
}
if (aPalette != NULL && strcmp(aPalette, "") != 0 && strcmp(aPalette, "default") != 0) {
if (aPalette != NULL && strcmp(aFilter, "") != 0 && strcmp(aFilter, "default") != 0) {
data->paletteTable = PaletteDB::Instance().FindPaletteTable(aPalette);
if (data->paletteTable == NULL) throw XMLParseException("Unknown palette specified!", name, data->pParser);
}
-1
View File
@@ -20,7 +20,6 @@ FLOAT gdMajorMapVersion = MAJOR_MAP_VERSION;
BOOLEAN gfWorldLoaded;
MAPCREATE_STRUCT gMapInformation;
MAPCREATE_STRUCT gMapInformationAutoResolve;
//CHRISL: MINOR_MAP_VERSION information moved to worlddef.h by ADB. We're using these values elsewhere and need them
// in the header file
-1
View File
@@ -57,7 +57,6 @@ public:
};
extern MAPCREATE_STRUCT gMapInformation;
extern MAPCREATE_STRUCT gMapInformationAutoResolve;
void LoadMapInformation(INT8** hBuffer, FLOAT dMajorMapVersion);
void SaveMapInformation(HWFILE hFile, FLOAT dMajorMapVersion, UINT8 ubMinorMapVersion);
+43 -15
View File
@@ -3026,7 +3026,7 @@ BOOLEAN HandleAtNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving )
// sevenfm: check all nearby enemy boxers for opportunity attack
if (IS_MERC_BODY_TYPE(pSoldier) &&
(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
BOXER(pSoldier) &&
gTacticalStatus.bBoxingState == BOXING &&
pSoldier->aiData.bAlertStatus >= STATUS_RED)
{
@@ -3047,7 +3047,7 @@ BOOLEAN HandleAtNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving )
pOpponent->bCollapsed ||
pOpponent->bBreathCollapsed ||
!IS_MERC_BODY_TYPE(pOpponent) ||
!(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) ||
!BOXER(pOpponent) ||
gAnimControl[pOpponent->usAnimState].ubEndHeight < ANIM_STAND ||
pOpponent->pathing.bLevel != pSoldier->pathing.bLevel ||
!SoldierToSoldierLineOfSightTest(pOpponent, pSoldier, TRUE, CALC_FROM_WANTED_DIR) ||
@@ -5031,11 +5031,11 @@ BOOLEAN NewOKDestination( SOLDIERTYPE * pCurrSoldier, INT32 sGridNo, BOOLEAN fPe
BOOLEAN fOKCheckStruct;
// Allow civilians and NPCs with profile to go off screen, and also enemies if tactical retreat is enabled
auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo));
auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE;
auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM;
auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM;
auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == true;
const auto destinationOffscreen = !(GridNoOnVisibleWorldTile(sGridNo));
const auto hasProfile = pCurrSoldier->ubProfile != NO_PROFILE;
const auto isCivilian = pCurrSoldier->bTeam == CIV_TEAM;
const auto isEnemy = pCurrSoldier->bTeam == ENEMY_TEAM;
const auto retreatAllowed = gGameExternalOptions.fAITacticalRetreat == TRUE;
if (destinationOffscreen && !(isCivilian || hasProfile || (isEnemy && retreatAllowed)))
{
@@ -5400,6 +5400,34 @@ BOOLEAN TeamMemberNear( INT8 bTeam, INT32 sGridNo, INT32 iRange )
return(FALSE);
}
BOOLEAN NotDeafTeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange)
{
for ( SoldierID pSoldier = gTacticalStatus.Team[bTeam].bFirstID; pSoldier <= gTacticalStatus.Team[bTeam].bLastID; ++pSoldier )
{
if ( pSoldier->bActive &&
pSoldier->bInSector &&
pSoldier->stats.bLife >= OKLIFE &&
pSoldier->bDeafenedCounter == 0 )
{
if ( PythSpacesAway(pSoldier->sGridNo, sGridNo) <= iRange )
{
return(TRUE);
}
}
}
return(FALSE);
}
BOOLEAN PlayerCanHearNoise(SOLDIERTYPE* pSoldier)
{
if ( pSoldier &&
(pSoldier->bVisible == TRUE || NotDeafTeamMemberNear(gbPlayerNum, pSoldier->sGridNo, TACTICAL_RANGE / 2) || NightLight() && !(gTacticalStatus.uiFlags & TURNBASED && gTacticalStatus.uiFlags & INCOMBAT) && NotDeafTeamMemberNear(gbPlayerNum, pSoldier->sGridNo, TACTICAL_RANGE)) )
return TRUE;
return FALSE;
}
INT32 FindAdjacentGridEx( SOLDIERTYPE *pSoldier, INT32 sGridNo, UINT8 *pubDirection, INT32 *psAdjustedGridNo, BOOLEAN fForceToPerson, BOOLEAN fDoor, bool allow_diagonal )
{
// psAdjustedGridNo gets the original gridno or the new one if updated
@@ -7175,7 +7203,7 @@ static void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8
//if ( pTeamSoldier->stats.bLife >= OKLIFE && pTeamSoldier->stats.bLife != 0 )
{
// officers and generals are 'special' prisoners...
if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP )
if (ISVIP(pTeamSoldier))
++sNumPrisoner[PRISONER_GENERAL];
// downed pilots count as officers too, even though they are civilians. This makes capturing them more rewarding
else if ( (pTeamSoldier->usSoldierFlagMask & SOLDIER_ENEMY_OFFICER) || pTeamSoldier->ubCivilianGroup == DOWNEDPILOT_CIV_GROUP )
@@ -7204,7 +7232,7 @@ static void RemoveCapturedEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8
}
// Flugente: VIPs
if ( pTeamSoldier->usSoldierFlagMask & SOLDIER_VIP )
if (ISVIP(pTeamSoldier))
DeleteVIP( pTeamSoldier->sSectorX, pTeamSoldier->sSectorY );
// Flugente: turncoats
@@ -9251,10 +9279,10 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
if ( gTacticalStatus.bBoxingState == BOXING )
{
// should have a check for "in boxing ring", no?
if ( ( pSoldier->usAttackingWeapon != NOTHING && !ItemIsBrassKnuckles(pSoldier->usAttackingWeapon)) || !( pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ) || pSoldier->IsRiotShieldEquipped() )
if ( ( pSoldier->usAttackingWeapon != NOTHING && !ItemIsBrassKnuckles(pSoldier->usAttackingWeapon)) || !BOXER(pSoldier) || pSoldier->IsRiotShieldEquipped() )
{
// someone's cheating!
if ( (Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_BLADE || Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_PUNCH) && (pTarget->flags.uiStatusFlags & SOLDIER_BOXER) )
if ( (Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_BLADE || Item[ pSoldier->usAttackingWeapon ].usItemClass == IC_PUNCH) && BOXER(pTarget) )
{
// knife or brass knuckles disqualify the player!
BoxingPlayerDisqualified( pSoldier, BAD_ATTACK );
@@ -9265,7 +9293,7 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
//gTacticalStatus.bBoxingState = NOT_BOXING;
SetBoxingState( NOT_BOXING );
// if we are attacking a boxer we should set them to neutral (temporarily) so that the rest of the civgroup code works...
if ( (pTarget->bTeam == CIV_TEAM) && (pTarget->flags.uiStatusFlags & SOLDIER_BOXER) )
if ( (pTarget->bTeam == CIV_TEAM) && BOXER(pTarget) )
{
SetSoldierNeutral( pTarget );
}
@@ -9412,7 +9440,7 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp
//TriggerNPCWithIHateYouQuote( pTarget->ubProfile );
}
}
else if ( pTarget->ubCivilianGroup != NON_CIV_GROUP && !( pTarget->flags.uiStatusFlags & SOLDIER_BOXER ) )
else if ( pTarget->ubCivilianGroup != NON_CIV_GROUP && !BOXER(pTarget) )
{
// Firing at a civ in a civ group who isn't hostile... if anyone in that civ group can see this
// going on they should become hostile.
@@ -10848,7 +10876,7 @@ static void TurnCoatAttemptMessageBoxCallBack( UINT8 ubExitValue )
UINT8 approachchance = gusSelectedSoldier->GetTurncoatConvinctionChance( prisonerdialoguetargetID, approachselected );
// you can never turn a VIP (though we don't tell the player if someone is a VIP, lest they have an exploit to find out)
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP )
if (ISVIP(pSoldier))
approachchance = 0;
// as using random numbers to pass the check would result in players savescumming, use a number based on the soldier's stats
@@ -11403,7 +11431,7 @@ void TeamDropAll(UINT8 bTeam, BOOLEAN fForce)
// if soldier is in the current sector, drop all equipment (that has the TAKEN_BY_MILITIA-flag set)
if( pSoldier->bActive && ( pSoldier->sSectorX == gWorldSectorX ) && ( pSoldier->sSectorY == gWorldSectorY ) && ( pSoldier->bSectorZ == gbWorldSectorZ) )
{
pSoldier->DropSectorEquipment( NOWHERE );
pSoldier->DropSectorEquipment();
}
}
}
+3 -1
View File
@@ -261,6 +261,9 @@ void SelectNextAvailSoldier( SOLDIERTYPE *pSoldier );
BOOLEAN TeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange);
BOOLEAN IsValidTargetMerc( SoldierID ubSoldierID );
BOOLEAN NotDeafTeamMemberNear(INT8 bTeam, INT32 sGridNo, INT32 iRange);
BOOLEAN PlayerCanHearNoise(SOLDIERTYPE* pSoldier);
// FUNCTIONS FOR MANIPULATING MERC SLOTS - A LIST OF ALL ACTIVE MERCS
INT32 GetFreeMercSlot( );
INT32 AddMercSlot( SOLDIERTYPE *pSoldier );
@@ -433,4 +436,3 @@ BOOLEAN IsFreeSlotAvailable( int aTeam );
void AttemptToCapturePlayerSoldiers();
#endif
+36 -100
View File
@@ -36,6 +36,9 @@
#include "BinaryHeap.hpp"
#include "opplist.h"
#include "Weapons.h"
#include "renderworld.h"
#include "Cheats.h"
#include "Handle UI.h"
//forward declarations of common classes to eliminate includes
class OBJECTTYPE;
@@ -56,19 +59,8 @@ extern BOOLEAN InGasSpot(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bLevel);
// skiplist has extra level of pointers every 4 elements, so a level 5is optimized for
// 4 to the power of 5 elements, or 2 to the power of 10, 1024
//#define PATHAI_VISIBLE_DEBUG
//#define PATHAI_SKIPLIST_DEBUG
#ifdef PATHAI_VISIBLE_DEBUG
#include "video.h"
//extern INT16 gsCoverValue[WORLD_MAX];
extern INT16 * gsCoverValue;
BOOLEAN gfDisplayCoverValues = TRUE;
BOOLEAN gfDrawPathPoints = TRUE;
#endif
BOOLEAN gfPlotPathToExitGrid = FALSE;
BOOLEAN gfRecalculatingExistingPathCost = FALSE;
UINT8 gubGlobalPathFlags = 0;
@@ -613,15 +605,12 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
ResetDebugInfoValues();
gRenderDebugInfoValues[ StartNode ] = 0;
PATHAI_VISIBLE_DEBUG_Counter = 1;
}
gsCoverValue[ StartNode ] = 0;
PATHAI_VISIBLE_DEBUG_Counter = 1;
#endif
//init other private data, mostly flags
endDir = lastDir = direction = startDir = 0;
@@ -786,12 +775,10 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
return 0;
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
SetRenderFlags( RENDER_FLAG_FULL );
}
#endif
// Count the number of steps, but keep it less than the max path length.
// Adjust the parent until it begins at the tail end of the max path length (or the dest if reachable)
@@ -874,12 +861,10 @@ int AStarPathfinder::GetPath(SOLDIERTYPE *s ,
sizePath = giPathDataSize;
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
SetRenderFlags( RENDER_FLAG_FULL );
}
#endif
#ifdef COUNT_PATHS
guiSuccessfulPathChecks++;
@@ -991,15 +976,13 @@ void AStarPathfinder::ExecuteAStarLogic()
SetAStarStatus(ParentNode, AStar_Closed);
//ClosedList.push_back(ParentNode);
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
if (gsCoverValue[ ParentNode ] > 0)
if (gRenderDebugInfoValues[ParentNode] > 0)
{
gsCoverValue[ ParentNode ] *= -1;
gRenderDebugInfoValues[ParentNode] *= -1;
}
}
#endif
// Shouldn't G and AP be the same thing?
INT16 baseGCost = GetAStarG(ParentNode);
@@ -1082,7 +1065,7 @@ void AStarPathfinder::ExecuteAStarLogic()
gpWorldLevelData[ CurrentNode ].ubExtFlags[0] |= MAPELEMENT_EXT_CLIMBPOINT;
gpWorldLevelData[ ParentNode ].ubExtFlags[1] |= MAPELEMENT_EXT_CLIMBPOINT;
#ifdef ROOF_DEBUG
gsCoverValue[CurrentNode] = 1;
gRenderDebugInfoValues[CurrentNode] = 1;
#endif
}
@@ -1180,26 +1163,13 @@ void AStarPathfinder::ExecuteAStarLogic()
int AStarH = CalcH();
int AStarF = (AStarG + extraGCoverCost) + AStarH;
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
if (gsCoverValue[CurrentNode] == 0x7F7F)
if (gRenderDebugInfoValues[CurrentNode] == 0x7FFFFFFF)
{
//gsCoverValue[CurrentNode] = PATHAI_VISIBLE_DEBUG_Counter++;
gsCoverValue[CurrentNode] = (INT16) AStarF;
gRenderDebugInfoValues[CurrentNode] = (INT16) AStarF;
}
/*
else if (gsCoverValue[CurrentNodeIndex] >= 0)
{
gsCoverValue[CurrentNodeIndex]++;
}
else
{
gsCoverValue[CurrentNodeIndex]--;
}
*/
}
#endif
//insert this node onto the heap
if (GetAStarStatus(CurrentNode) == AStar_Init)
@@ -1380,7 +1350,7 @@ INT16 AStarPathfinder::CalcAP(int const terrainCost, UINT8 const direction)
}
// Flugente: dragging someone
if ( pSoldier->IsDragging() )
if ( pSoldier->IsDragging( false ) )
{
movementAPCost *= gItemSettings.fDragAPCostModifier;
}
@@ -2250,9 +2220,7 @@ INT32 FindBestPath(SOLDIERTYPE *s , INT32 sDestination, INT8 bLevel, INT16 usMov
CHAR8 zTempString[1000], zTS[50];
#endif
#ifdef PATHAI_VISIBLE_DEBUG
UINT16 usCounter = 0;
#endif
fVehicle = FALSE;
iOriginationX = iOriginationY = 0;
@@ -2502,12 +2470,10 @@ if(!GridNoOnVisibleWorldTile(iDestination))
memset( pathQ, 0, iMaxPathQ * sizeof( path_t ) );
memset( trailTree, 0, iMaxTrailTree * sizeof( trail_t ) );
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
ResetDebugInfoValues();
}
#endif
bSkipListLevel = 1;
iSkipListSize = 0;
@@ -2619,15 +2585,13 @@ if(!GridNoOnVisibleWorldTile(iDestination))
// remember the cost used to get here...
prevCost = gubWorldMovementCosts[ trailTree[ sCurPathNdx ].sGridNo ][ trailTree[ sCurPathNdx ].stepDir ][ bLevel ];
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
if (gsCoverValue[ curLoc ] > 0)
if (gRenderDebugInfoValues[ curLoc ] > 0)
{
gsCoverValue[ curLoc ] *= -1;
gRenderDebugInfoValues[ curLoc ] *= -1;
}
}
#endif
/*
if (fTurnSlow)
@@ -3553,27 +3517,13 @@ if(!GridNoOnVisibleWorldTile(iDestination))
// costs less than the best so far to the same location?
if (trailCostUsed[newLoc] != gubGlobalPathCount || newTotCost < trailCost[newLoc])
{
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
if (gsCoverValue[newLoc] == 0x7F7F)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
if (gRenderDebugInfoValues[newLoc] == 0x7FFFFFFF)
{
gsCoverValue[newLoc] = usCounter++;
gRenderDebugInfoValues[newLoc] = usCounter++;
}
/*
else if (gsCoverValue[newLoc] >= 0)
{
gsCoverValue[newLoc]++;
}
else
{
gsCoverValue[newLoc]--;
}
*/
}
#endif
}
//NEWQUENODE;
{
@@ -3816,20 +3766,13 @@ ENDOFLOOP:
while (pathQNotEmpty && pathNotYetFound);
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
if ( guiCurrentScreen == GAME_SCREEN )
{
SetRenderFlags( RENDER_FLAG_FULL );
if ( guiCurrentScreen == GAME_SCREEN )
{
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y);
}
#endif
}
// work finished. Did we find a path?
@@ -3884,17 +3827,10 @@ ENDOFLOOP:
}
#if defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues && gfDrawPathPoints)
{
SetRenderFlags( RENDER_FLAG_FULL );
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
}
#endif
if (gRenderDebugInfoMode == DEBUG_PATHFINDING && DEBUG_CHEAT_LEVEL())
{
InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y);
}
// return path length : serves as a "successful" flag and a path length counter
+7 -9
View File
@@ -713,10 +713,6 @@ void RemoveCorpse( INT32 iCorpseID )
DeleteAniTile( gRottingCorpse[ iCorpseID ].pAniTile );
FreeCorpsePalettes( &( gRottingCorpse[ iCorpseID ] ) );
const auto sGridNo = gRottingCorpse[iCorpseID].def.sGridNo;
auto pStructure = FindLastStructure(sGridNo, STRUCTURE_CORPSE);
DeleteStructureFromWorld(pStructure);
}
BOOLEAN CreateCorpsePalette( ROTTING_CORPSE *pCorpse )
@@ -1066,7 +1062,7 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE
DropKeysInKeyRing( pSoldier, pSoldier->sGridNo, pSoldier->pathing.bLevel, bVisible, FALSE, 0, FALSE );
// Flugente: even if we forbid militia from dropping their equipment, they will still drop what they took via sector inventory (this functions only drops what they took)
pSoldier->DropSectorEquipment( NOWHERE );
pSoldier->DropSectorEquipment();
}
// Make team look for items
@@ -1112,8 +1108,7 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE
iCorpseID = AddRottingCorpse( &Corpse );
// If this is our guy......make visible...
//if ( pSoldier->bTeam == gbPlayerNum )
if ( iCorpseID != -1 )
if ( iCorpseID != -1 && pSoldier->bTeam == OUR_TEAM || gbPublicOpplist[OUR_TEAM][pSoldier->ubID] == SEEN_CURRENTLY )
{
MakeCorpseVisible( pSoldier, &( gRottingCorpse[ iCorpseID ] ) );
}
@@ -1775,10 +1770,10 @@ BOOLEAN IsValidDecapitationCorpse( ROTTING_CORPSE *pCorpse )
ROTTING_CORPSE *GetCorpseAtGridNo( INT32 sGridNo, INT8 bLevel )
{
STRUCTURE *pStructure, *pBaseStructure;
STRUCTURE *pStructure, *pBaseStructure;
INT32 sBaseGridNo;
pStructure = FindLastStructure( sGridNo, STRUCTURE_CORPSE );
pStructure = FindStructure( sGridNo, STRUCTURE_CORPSE );
if ( pStructure != NULL )
{
@@ -2540,6 +2535,9 @@ void LookForAndMayCommentOnSeeingCorpse( SOLDIERTYPE *pSoldier, INT32 sGridNo, U
return;
}
if (pCorpse->fActivated && pCorpse->def.bVisible != 1)
pCorpse->def.bVisible = 1;
if ( pCorpse->def.ubType != ROTTING_STAGE2 )
{
return;
+1 -1
View File
@@ -3031,7 +3031,7 @@ BOOLEAN RepairIsDone(DEALER_SPECIAL_ITEM* pSpecial)
// max condition of all permanent attachments on it
for (attachmentList::iterator iter = RepairItem.ItemObject[0]->attachments.begin(); iter != RepairItem.ItemObject[0]->attachments.end(); ++iter) {
if ( iter->exists() && CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) )
if ( CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) && iter->exists())
{
// fix it up
(*iter)[0]->data.objectStatus = 100;
+1 -1
View File
@@ -4223,7 +4223,7 @@ BOOLEAN HandleSoldierDeath( SOLDIERTYPE *pSoldier , BOOLEAN *pfMadeCorpse )
}
// Flugente: VIPs
if ( pSoldier->usSoldierFlagMask & SOLDIER_VIP )
if (ISVIP(pSoldier))
{
DeleteVIP( pSoldier->sSectorX, pSoldier->sSectorY );
}
+97 -8
View File
@@ -9046,7 +9046,7 @@ void CalculateSoldierAniSpeed( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pStatsSoldier
}
// Flugente: drag people
if ( pSoldier->IsDragging() )
if ( pSoldier->IsDragging( false ) )
{
pSoldier->sAniDelay = gItemSettings.fDragAPCostModifier * pSoldier->sAniDelay;
}
@@ -9700,6 +9700,85 @@ void SOLDIERTYPE::BeginSoldierClimbWallUp( void )
}
//------------------------------------------------------------------------------------------
void SOLDIERTYPE::BeginSoldierJumpWindowAI(void)
{
DebugAI(AI_MSG_INFO, this, String("check if we can jump through window"));
//UINT8 ubDirection = this->aiData.usActionData;
UINT8 ubDirection = this->ubDirection;
INT32 sWindowGridNo = this->sGridNo;
if ( ubDirection == NORTH || ubDirection == WEST )
sWindowGridNo = NewGridNo(this->sGridNo, (UINT16)DirectionInc((UINT8)ubDirection));
DebugAI(AI_MSG_INFO, this, String("sWindowGridNo %d direction %d", sWindowGridNo, ubDirection));
if (//CheckWindow(this->sGridNo, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows) &&
IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows) &&
//FindWindowJumpDirection(this, this->sGridNo, bDirection, &bDirection) &&
this->pathing.bLevel == 0 &&
(ubDirection == NORTH || ubDirection == EAST || ubDirection == SOUTH || ubDirection == WEST) )
{
// Flugente: if we are jumping through an intact window, smash it during our animation
if ( gGameExternalOptions.fCanJumpThroughClosedWindows )
{
// is there really an intact window that we jump through?
if ( IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, TRUE) && !IsJumpableWindowPresentAtGridNo(sWindowGridNo, ubDirection, FALSE) )
{
STRUCTURE* pStructure = FindStructure(sWindowGridNo, STRUCTURE_WALLNWINDOW);
if ( pStructure && !(pStructure->fFlags & STRUCTURE_OPEN) )
{
DebugAI(AI_MSG_INFO, this, String("jumping through closed window, damage soldier"));
// intact window found. Smash it!
WindowHit(sWindowGridNo, pStructure->usStructureID, (ubDirection == SOUTH || ubDirection == EAST), TRUE, PlayerCanHearNoise(this));
// we get a bit of damage for jumping through a window
this->SoldierTakeDamage(0, 2 + 2 * Random(4), 0, 1000, TAKE_DAMAGE_ELECTRICITY, NOBODY, sWindowGridNo, 0);
}
}
}
this->sTempNewGridNo = NewGridNo(this->sGridNo, (UINT16)DirectionInc(ubDirection));
this->flags.fDontChargeTurningAPs = TRUE;
EVENT_InternalSetSoldierDesiredDirection(this, ubDirection, FALSE, this->usAnimState);
this->flags.fTurningUntilDone = TRUE;
// ATE: Reset flag to go back to prone...
// Flugente: In case an animation is missing (civilian bodytypes), we TELEPORT instead
if ( IsAnimationValidForBodyType(this, JUMPWINDOWS) == FALSE )
{
DebugAI(AI_MSG_INFO, this, String("teleport soldier to %d", this->sTempNewGridNo));
// sevenfm: deduct APs for jumping
if ( UsingNewInventorySystem() && FindBackpackOnSoldier(this) != ITEM_NOT_FOUND )
DeductPoints(this, GetAPsToJumpThroughWindows(this, TRUE), GetBPsToJumpThroughWindows(this, TRUE), SP_MOVEMENT_INTERRUPT);
else
DeductPoints(this, GetAPsToJumpThroughWindows(this, FALSE), GetBPsToJumpThroughWindows(this, FALSE), SP_MOVEMENT_INTERRUPT);
TeleportSoldier(this, this->sTempNewGridNo, TRUE);
}
else
{
DebugAI(AI_MSG_INFO, this, String("start jumping"));
this->flags.bTurningFromPronePosition = TURNING_FROM_PRONE_OFF;
this->EVENT_InitNewSoldierAnim(JUMPWINDOWS, 0, FALSE);
//this->usPendingAnimation = JUMPWINDOWS;
//EndAIGuysTurn(this);
}
// Flugente: should be fixed now, re-enable if not
// Flugente: if an AI guy, end turn (weird endless clock syndrome)
//if ( this->bTeam != OUR_TEAM )
//EndAIGuysTurn( this);
}
else
{
ScreenMsg(FONT_LTRED, MSG_INTERFACE, L"[%d] %s cannot jump", this->ubID, this->GetName());
DebugAI(AI_MSG_INFO, this, String("CancelAIAction: cannot jump"));
CancelAIAction(this, TRUE);
}
}
UINT32 SleepDartSuccumbChance( SOLDIERTYPE * pSoldier )
{
UINT32 uiChance;
@@ -9853,7 +9932,7 @@ void SOLDIERTYPE::BeginSoldierGetup( void )
else
{
this->bTurnsCollapsed++;
if ( (gTacticalStatus.bBoxingState == BOXING) && (this->flags.uiStatusFlags & SOLDIER_BOXER) )
if ( (gTacticalStatus.bBoxingState == BOXING) && (BOXER(this)) )
{
if ( this->bTurnsCollapsed > 1 )
{
@@ -11516,7 +11595,7 @@ void SOLDIERTYPE::MoveMerc( FLOAT dMovementChange, FLOAT dAngle, BOOLEAN fCheckR
// Flugente: as we move a tile, we would now be too far away to drag someone.
// So remember whether we were dragging (we have to set our position now, otherwise the person we drag woul soon occupy our gridno).
BOOLEAN currentlydragging = this->IsDragging(true);
BOOLEAN currentlydragging = this->IsDragging();
INT32 sOldGridNo = this->sGridNo;
// OK, set new position
@@ -14668,7 +14747,7 @@ void SOLDIERTYPE::SoldierInventoryCoolDown( void )
attachmentList::iterator iterend = (*pObj)[0]->attachments.end( );
for ( attachmentList::iterator iter = (*pObj)[0]->attachments.begin( ); iter != iterend; ++iter )
{
if ( iter->exists() && Item[iter->usItem].usFlashLightRange )
if ( Item[iter->usItem].usFlashLightRange )
flashlightfound = TRUE;
}
}
@@ -16818,7 +16897,7 @@ BOOLEAN SOLDIERTYPE::UpdateMultiTurnAction( )
return TRUE;
}
void SOLDIERTYPE::DropSectorEquipment( INT32 UnloadedSectorGridNo)
void SOLDIERTYPE::DropSectorEquipment( )
{
// not if we already dropped the gear
if ( this->usSoldierFlagMask & SOLDIER_EQUIPMENT_DROPPED )
@@ -16887,7 +16966,7 @@ void SOLDIERTYPE::DropSectorEquipment( INT32 UnloadedSectorGridNo)
}
}
AddItemsToUnLoadedSector( this->sSectorX, this->sSectorY, this->bSectorZ, UnloadedSectorGridNo, counter, pObject, 0, WORLD_ITEM_REACHABLE, 0, 1, FALSE );
AddItemsToUnLoadedSector( this->sSectorX, this->sSectorY, this->bSectorZ, RandomGridNo( ), counter, pObject, 0, WORLD_ITEM_REACHABLE, 0, 1, FALSE );
}
}
@@ -19046,7 +19125,7 @@ BOOLEAN SOLDIERTYPE::IsJamming( )
{
if ( CanUseRadio( FALSE ) )
return TRUE;
// if we cannot use the radio, remove that flag hile we're at it
// if we cannot use the radio, remove that flag while we're at it
else
usSoldierFlagMask &= ~SOLDIER_RADIO_OPERATOR_JAMMING;
}
@@ -24226,7 +24305,7 @@ BOOLEAN SOLDIERTYPE::CanBreakWindow(void)
BOOLEAN SOLDIERTYPE::CanStartDrag(void)
{
if (!this->IsDragging() && this->CanDragInPrinciple())
if (!this->IsDragging(false) && this->CanDragInPrinciple())
{
INT32 sNewGridNo = NewGridNo(this->sGridNo, DirectionInc(this->ubDirection));
@@ -26297,3 +26376,13 @@ void SOLDIERTYPE::InitializeExtraData(void)
this->delayedDamageFunction = nullptr;
}
UINT8 SOLDIERTYPE::AnimHeight(void) const
{
return gAnimControl[this->usAnimState].ubHeight;
}
UINT8 SOLDIERTYPE::AnimEndHeight(void) const
{
return gAnimControl[this->usAnimState].ubEndHeight;
}
+5 -2
View File
@@ -1788,10 +1788,13 @@ public:
void ChangeToFallbackAnimation( UINT8 fallBackDirection );
// sevenfm
void BeginSoldierJumpWindowAI(void);
void BreakWindow(void);
BOOLEAN CanBreakWindow(void);
BOOLEAN CanStartDrag(void);
void StartDrag(void);
UINT8 AnimHeight(void) const;
UINT8 AnimEndHeight(void) const;
void UpdateRobotControllerGivenController( void );
void UpdateRobotControllerGivenRobot( void );
@@ -1916,7 +1919,7 @@ public:
void CancelMultiTurnAction(BOOLEAN fFinished);
BOOLEAN UpdateMultiTurnAction();
void DropSectorEquipment( INT32 UnloadedSectorGridNo);
void DropSectorEquipment();
// sevenfm: Take new bomb with id = usItem from iventory to HANDPOS
void TakeNewBombFromInventory(UINT16 usItem);
@@ -2056,7 +2059,7 @@ public:
BOOLEAN CanDragPerson(SoldierID usID, BOOLEAN fCheckStance = FALSE);
BOOLEAN CanDragCorpse(UINT16 usCorpseNum, BOOLEAN fCheckStance = FALSE);
BOOLEAN CanDragStructure(INT32 sGridNo, BOOLEAN fCheckStance = FALSE);
BOOLEAN IsDragging(bool aStopIfConditionNotSatisfied = false);
BOOLEAN IsDragging(bool aStopIfConditionNotSatisfied = true);
void SetDragOrderPerson( SoldierID usID );
void SetDragOrderCorpse( UINT32 uiCorpseID );
void SetDragOrderStructure( INT32 sGridNo );
+3 -1
View File
@@ -37,8 +37,10 @@
#define TANK( p ) (p->ubBodyType == TANK_NE || p->ubBodyType == TANK_NW )
#define ENEMYROBOT( p ) (p->ubBodyType == ROBOTNOWEAPON && p->bTeam == ENEMY_TEAM)
#define ARMED_VEHICLE( p ) ( TANK( p ) || COMBAT_JEEP(p) )
#define BOXER( p ) ( p->flags.uiStatusFlags & SOLDIER_BOXER )
#define ISVIP( p ) ( p->usSoldierFlagMask & SOLDIER_VIP )
//#define OK_ENTERABLE_VEHICLE( p ) ( ( p->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !TANK( p ) && p->stats.bLife >= OKLIFE )
#define OK_ENTERABLE_VEHICLE( p ) ( ( p->flags.uiStatusFlags & SOLDIER_VEHICLE ) && (!ARMED_VEHICLE( p ) || !(p->flags.uiStatusFlags & SOLDIER_ENEMY) ) && p->stats.bLife >= OKLIFE )
#endif
#endif
+1 -1
View File
@@ -147,7 +147,7 @@ void SoldierTooltip( SOLDIERTYPE* pSoldier )
{
OBJECTTYPE* pObject = &(gusSelectedSoldier->inv[HANDPOS]);
for (attachmentList::iterator iter = (*pObject)[0]->attachments.begin(); iter != (*pObject)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].visionrangebonus > 0 )
if ( Item[iter->usItem].visionrangebonus > 0 && iter->exists())
{
fMercIsUsingScope = TRUE;
break;
+2 -3
View File
@@ -1218,11 +1218,10 @@ void EndInterrupt( BOOLEAN fMarkInterruptOccurred )
else
{
ubInterruptedSoldier = LATEST_INTERRUPT_GUY;
DebugMsg( TOPIC_JA2INTERRUPT, DBG_LEVEL_3, String("INTERRUPT: interrupt over, %d's team regains control", ubInterruptedSoldier ) );
pSoldier = ubInterruptedSoldier;
DebugMsg( TOPIC_JA2INTERRUPT, DBG_LEVEL_3, String("INTERRUPT: interrupt over, soldier %d's team %d regains control", ubInterruptedSoldier, pSoldier->bTeam ) );
for ( SoldierID id = 0; id < MAX_NUM_SOLDIERS; ++id)
{
pTempSoldier = id;
+38 -17
View File
@@ -1684,6 +1684,24 @@ void ItemCreationCallBack( UINT8 ubResult )
memset(gszMsgBoxInputString,0,sizeof(gszMsgBoxInputString));
}
static void CycleThroughTileDebugInfo()
{
const STR16 modeStrings[] =
{
L"Debug draw mode: Pathfinding",
L"Debug drawmode: Threat values",
L"Debug drawmode: Cover values",
L"Debug drawmode: Off",
};
gRenderDebugInfoMode += 1;
if (gRenderDebugInfoMode > DEBUG_OFF)
{
gRenderDebugInfoMode = 0;
}
ScreenMsg(FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, modeStrings[gRenderDebugInfoMode]);
}
extern BOOLEAN gfDisableRegionActive;
extern BOOLEAN gfUserTurnRegionActive;
@@ -2221,7 +2239,7 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
// sevenfm: also stop dragging
if (gusSelectedSoldier != NOBODY &&
gusSelectedSoldier &&
gusSelectedSoldier->IsDragging())
gusSelectedSoldier->IsDragging(false))
{
gusSelectedSoldier->CancelDrag();
DirtyMercPanelInterface(gusSelectedSoldier, DIRTYLEVEL2);
@@ -4303,25 +4321,21 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
// Make auto reload with magazines from sector inventory
case 'R':
if (fAlt && gusSelectedSoldier != NOBODY ) // Greysa: Moved here from 'r' to retain all functionality during testing.
{
if (CHEATER_CHEAT_LEVEL())
{
ReloadWeapon(gusSelectedSoldier, gusSelectedSoldier->ubAttackingHand);
}
}
else
{
HandleTBReloadAll();
}
HandleTBReloadAll();
break;
case 'r':
if( gusSelectedSoldier != NOBODY )
{
if( fAlt )
if( fAlt ) //reload selected merc's weapon
{
HandleTBReload();
}
if ( CHEATER_CHEAT_LEVEL( ) )
{
ReloadWeapon( gusSelectedSoldier, gusSelectedSoldier->ubAttackingHand );
}
else
HandleTBReload();
}
else if( fCtrl )
{
if ( INFORMATION_CHEAT_LEVEL( ) )
@@ -4756,6 +4770,13 @@ void GetKeyboardInput( UINT32 *puiNewEvent )
break;
case 'Z':
if (fCtrl)
{
if (DEBUG_CHEAT_LEVEL())
{
CycleThroughTileDebugInfo();
}
}
break;
}
@@ -6770,7 +6791,7 @@ void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision)
pObj = &(pTeamSoldier->inv[gear]);
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE )
if ( Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists())
{
pGoggles = &(*iter);
bestBonus = Item[ iter->usItem ].nightvisionrangebonus;
@@ -6818,7 +6839,7 @@ void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision)
pObj = &(pTeamSoldier->inv[gear]);
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE )
if ( Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE && iter->exists())
{
pGoggles = &(*iter);
bestBonus = Item[ iter->usItem ].brightlightvisionrangebonus;
+1 -1
View File
@@ -2081,7 +2081,7 @@ UINT8 HandleNonActivatedTossCursor( SOLDIERTYPE *pSoldier, INT32 sGridNo, BOOLEA
//sevenfm this should be checked only for guns because we can throw items with attached explosives
if(Item[pObj->usItem].usItemClass & IC_WEAPON )
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[ iter->usItem ].usItemClass & IC_EXPLOSV )
if ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV && iter->exists())
{
pObject = &(*iter);
break;
+24 -22
View File
@@ -1036,7 +1036,7 @@ INT32 EffectiveArmour( OBJECTTYPE * pObj )
iValue = iValue * (*pObj)[0]->data.objectStatus * Armour[ Item[pObj->usItem].ubClassIndex ].ubCoverage / 10000;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists())
{
INT32 iValue2;
@@ -1062,7 +1062,7 @@ INT32 EffectiveArmourLBE( OBJECTTYPE * pObj )
iValue = 0;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{
INT32 iValue2;
@@ -1088,7 +1088,7 @@ INT32 ExplosiveEffectiveArmourLBE( OBJECTTYPE * pObj )
iValue = 0;
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{
INT32 iValue2;
@@ -1214,7 +1214,7 @@ INT32 ExplosiveEffectiveArmour( OBJECTTYPE * pObj )
}
for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists())
{
INT32 iValue2;
@@ -1295,7 +1295,7 @@ INT16 FireEffectiveArmour( OBJECTTYPE * pObj )
for ( attachmentList::iterator iter = ( *pObj )[0]->attachments.begin(); iter != ( *pObj )[0]->attachments.end(); ++iter )
{
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && ( *iter )[0]->data.objectStatus > 0 )
if ( Item[iter->usItem].usItemClass == IC_ARMOUR && ( *iter )[0]->data.objectStatus > 0 && iter->exists() )
{
iValue += Item[iter->usItem].sFireResistance * ( *iter )[0]->data.objectStatus / 100;
}
@@ -2560,7 +2560,7 @@ BOOLEAN UseGunNCTH( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo )
{
for (attachmentList::iterator iter = (*pObjAttHand)[0]->attachments.begin(); iter != (*pObjAttHand)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && ItemHasHiddenMuzzleFlash(iter->usItem))
if (ItemHasHiddenMuzzleFlash(iter->usItem))
{
OBJECTTYPE* pA= &(*iter);
if ( (*pA)[0]->data.objectStatus >=USABLE)
@@ -3346,7 +3346,7 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo )
{
for (attachmentList::iterator iter = (*pObjUsed)[0]->attachments.begin(); iter != (*pObjUsed)[0]->attachments.end(); ++iter)
{
if ( iter->exists() && ItemHasHiddenMuzzleFlash(iter->usItem))
if (ItemHasHiddenMuzzleFlash(iter->usItem))
{
OBJECTTYPE* pA= &(*iter);
if ( (*pA)[0]->data.objectStatus >=USABLE)
@@ -4257,10 +4257,10 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
// sevenfm: bonus for boxers for attack from the back
if (iHitChance < 100 &&
(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
BOXER(pSoldier) &&
!pSoldier->bBlindedCounter &&
gAnimControl[pTargetSoldier->usAnimState].ubEndHeight > ANIM_PRONE &&
(pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
BOXER(pTargetSoldier) &&
pTargetSoldier->usSoldierFlagMask2 & SOLDIER_BACK_ATTACK)
{
iHitChance += (100 - iHitChance) / 2;
@@ -4876,7 +4876,7 @@ BOOLEAN UseHandToHand( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, BOOLEAN fStea
if (pTargetSoldier->bActionPoints > 0 &&
gGameOptions.fNewTraitSystem &&
gTacticalStatus.bBoxingState == BOXING &&
(pTargetSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
BOXER(pTargetSoldier) &&
Chance(ubCounterattackChance) &&
IS_MERC_BODY_TYPE(pSoldier) &&
IS_MERC_BODY_TYPE(pTargetSoldier) &&
@@ -5111,7 +5111,7 @@ BOOLEAN UseLauncher( SOLDIERTYPE *pSoldier, INT32 sTargetGridNo )
pObj = &(pSoldier->inv[HANDPOS]);
attachmentList::iterator iter;
for (iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[ iter->usItem ].usItemClass & IC_EXPLOSV )
if ( Item[ iter->usItem ].usItemClass & IC_EXPLOSV && iter->exists())
{
break;
}
@@ -6089,7 +6089,7 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, Sol
}
}
void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce )
void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound)
{
STRUCTURE * pWallAndWindow;
DB_STRUCTURE * pWallAndWindowInDB;
@@ -6210,10 +6210,12 @@ void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, B
pNode = CreateAnimationTile( &AniParams );
//ddd window{
CompileWorldMovementCosts();
//ddd window}
PlayJA2Sample( GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir( sGridNo ) );
CompileWorldMovementCosts();
//ddd window}
if ( fSound )
{
PlayJA2Sample(GLASS_SHATTER1 + Random(2), RATE_11025, MIDVOLUME, 1, SoundDir(sGridNo));
}
}
@@ -7993,7 +7995,7 @@ INT32 TotalArmourProtection( SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 i
if (iSlot == VESTPOS && pVestPack->exists() == true)
{
for (attachmentList::iterator iter = (*pVestPack)[0]->attachments.begin(); iter != (*pVestPack)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
{
INT32 protection = ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit );
@@ -8025,7 +8027,7 @@ INT32 TotalArmourProtection( SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 i
if (pArmour->exists() == true)
{
for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists())
{
// bullet got through jacket; apply ceramic plate armour
INT32 protection = ArmourProtection( pTarget, Item[iter->usItem].ubClassIndex, &((*iter)[0]->data.objectStatus), iImpact, ubAmmoType, &plateHit );
@@ -8071,7 +8073,7 @@ INT32 TotalArmourProtection( SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 i
{
//Madd: put any attachments that someone might have added to the armour in the merc's inventory
for (attachmentList::iterator iter = (*pArmour)[0]->attachments.begin(); iter != (*pArmour)[0]->attachments.end(); ++iter) {
if ( iter->exists() && !AutoPlaceObject( pTarget, &(*iter), FALSE ) )
if ( !AutoPlaceObject( pTarget, &(*iter), FALSE ) && iter->exists())
{ // put it on the ground
AddItemToPool( pTarget->sGridNo, &(*iter), 1, pTarget->pathing.bLevel, 0 , -1 );
}
@@ -9723,7 +9725,7 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi
{
// Changed from DG by CJC to give higher chances of hitting with a stab or punch
// sevenfm: lowered chance for boxers
if (pAttacker->flags.uiStatusFlags & SOLDIER_BOXER)
if (BOXER(pAttacker))
iChance = 50 + (iAttRating - iDefRating) / 3;
else
iChance = 67 + (iAttRating - iDefRating) / 3;
@@ -9778,8 +9780,8 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi
// sevenfm: bonus for boxers for attacking from the back
if (ubMode == HTH_MODE_PUNCH &&
(pAttacker->flags.uiStatusFlags & SOLDIER_BOXER) &&
(pDefender->flags.uiStatusFlags & SOLDIER_BOXER) &&
BOXER(pAttacker) &&
BOXER(pDefender) &&
iChance < 100 &&
!pAttacker->bBlindedCounter &&
gAnimControl[pDefender->usAnimState].ubEndHeight > ANIM_PRONE &&
+1 -1
View File
@@ -456,7 +456,7 @@ INT16 ArmourVersusFirePercent( SOLDIERTYPE * pSoldier );
extern BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo );
extern void WeaponHit( SoldierID usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, UINT16 usDirection, INT16 sXPos, INT16 sYPos, INT16 sZPos, INT16 sRange, SoldierID ubAttackerID, BOOLEAN fHit, UINT8 ubSpecial, UINT8 ubHitLocation );
extern void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, SoldierID ubAttackerID, UINT16 sXPos, INT16 sYPos, INT16 sZPos, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped );
extern void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce );
void WindowHit(INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce, BOOLEAN fSound = TRUE);
// HEADROCK HAM 5.1: Moved to Bullets.h
extern BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT32 sGridNo );
extern void ShotMiss( SoldierID ubAttackerID, INT32 iBullet );
+5 -4
View File
@@ -4,7 +4,7 @@
#include "Overhead.h"
#include "random.h"
#include "Points.h"
#include "ai.h"
#include <iostream>
#include <iomanip>
@@ -190,7 +190,6 @@ typedef enum
INT16 AdvanceToFiringRange( SOLDIERTYPE * pSoldier, INT16 sClosestOpponent );
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier);
void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot);
void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAttack);
void CalcBestThrow(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow);
@@ -223,7 +222,6 @@ INT8 ArmedVehicleDecideAction( SOLDIERTYPE* pSoldier );
// a variant of ClosestSeenOpponent(...), that allows to find enemies on a roof
INT32 ClosestSeenOpponentWithRoof(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel);
INT8 CrowDecideAction( SOLDIERTYPE * pSoldier );
void DecideAlertStatus( SOLDIERTYPE *pSoldier );
INT8 DecideAutoBandage( SOLDIERTYPE * pSoldier );
UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction );
@@ -241,9 +239,12 @@ INT32 GetInterveningClimbingLocation( SOLDIERTYPE * pSoldier, INT32 sDestGridNo,
UINT8 GetTraversalQuoteActionID( INT8 bDirection );
INT32 GoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT8 bAction);
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier );
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier );
BOOLEAN InSmoke(INT32 sGridNo, INT8 bLevel);
BOOLEAN InSmoke(SOLDIERTYPE* pSoldier, INT32 sGridNo);
BOOLEAN InTearGas(SOLDIERTYPE* pSoldier, INT32 sGridNo);
BOOLEAN InMustardGas(SOLDIERTYPE* pSoldier, INT32 sGridNo);
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo );
BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo );
BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo );
+2 -1
View File
@@ -18,6 +18,7 @@
#include "opplist.h"
#include "Interface.h"
#include "Tactical Save.h"
#include <Soldier macros.h>
#define AI_LIST_SIZE TOTAL_SOLDIERS
@@ -180,7 +181,7 @@ BOOLEAN InsertIntoAIList( SoldierID ubID, INT8 bPriority )
BOOLEAN SatisfiesAIListConditions( SOLDIERTYPE * pSoldier, UINT16 * pubDoneCount, BOOLEAN fDoRandomChecks )
{
if ( (gTacticalStatus.bBoxingState == BOXING) && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) )
if ( (gTacticalStatus.bBoxingState == BOXING) && !BOXER(pSoldier) )
{
return( FALSE );
}
+328 -107
View File
@@ -49,6 +49,7 @@
#include "Soldier Functions.h" // added by SANDRO
#include "Text.h" // sevenfm
#include "english.h" // sevenfm: for ESC key
#include "Food.h"
#include "connect.h"
// needed to use the modularized tactical AI:
@@ -206,18 +207,96 @@ STR szAction[] = {
"AI_ACTION_STOP_MEDIC"
};
STR16 wszAction[] = {
L"AI_ACTION_NONE",
L"AI_ACTION_RANDOM_PATROL",
L"AI_ACTION_SEEK_FRIEND",
L"AI_ACTION_SEEK_OPPONENT",
L"AI_ACTION_TAKE_COVER",
L"AI_ACTION_GET_CLOSER",
L"AI_ACTION_POINT_PATROL",
L"AI_ACTION_LEAVE_WATER_GAS",
L"AI_ACTION_SEEK_NOISE",
L"AI_ACTION_ESCORTED_MOVE",
L"AI_ACTION_RUN_AWAY",
L"AI_ACTION_KNIFE_MOVE",
L"AI_ACTION_APPROACH_MERC",
L"AI_ACTION_TRACK",
L"AI_ACTION_EAT",
L"AI_ACTION_PICKUP_ITEM",
L"AI_ACTION_SCHEDULE_MOVE",
L"AI_ACTION_WALK",
L"AI_ACTION_RUN",
L"AI_ACTION_WITHDRAW",
L"AI_ACTION_FLANK_LEFT",
L"AI_ACTION_FLANK_RIGHT",
L"AI_ACTION_MOVE_TO_CLIMB",
L"AI_ACTION_CHANGE_FACING",
L"AI_ACTION_CHANGE_STANCE",
L"AI_ACTION_YELLOW_ALERT",
L"AI_ACTION_RED_ALERT",
L"AI_ACTION_CREATURE_CALL",
L"AI_ACTION_PULL_TRIGGER",
L"AI_ACTION_USE_DETONATOR",
L"AI_ACTION_FIRE_GUN",
L"AI_ACTION_TOSS_PROJECTILE",
L"AI_ACTION_KNIFE_STAB",
L"AI_ACTION_THROW_KNIFE",
L"AI_ACTION_GIVE_AID",
L"AI_ACTION_WAIT",
L"AI_ACTION_PENDING_ACTION",
L"AI_ACTION_DROP_ITEM",
L"AI_ACTION_COWER",
L"AI_ACTION_STOP_COWERING",
L"AI_ACTION_OPEN_OR_CLOSE_DOOR",
L"AI_ACTION_UNLOCK_DOOR",
L"AI_ACTION_LOCK_DOOR",
L"AI_ACTION_LOWER_GUN",
L"AI_ACTION_ABSOLUTELY_NONE",
L"AI_ACTION_CLIMB_ROOF",
L"AI_ACTION_END_TURN",
L"AI_ACTION_END_COWER_AND_MOVE",
L"AI_ACTION_TRAVERSE_DOWN",
L"AI_ACTION_OFFER_SURRENDER",
L"AI_ACTION_RAISE_GUN",
L"AI_ACTION_STEAL_MOVE",
L"AI_ACTION_RELOAD_GUN",
L"AI_ACTION_JUMP_WINDOW",
L"AI_ACTION_FREE_PRISONER",
L"AI_ACTION_USE_SKILL",
L"AI_ACTION_DOCTOR",
L"AI_ACTION_DOCTOR_SELF",
L"AI_ACTION_SELFDETONATE",
L"AI_ACTION_STOP_MEDIC"
};
// sevenfm
UINT32 guiAIStartCounter = 0, guiAILastCounter = 0;
//UINT8 gubAISelectedSoldier = NOBODY;
BOOLEAN gfLogsEnabled = TRUE;
bool gLogDecideActionRed = true;
bool gLogDecideActionBlack = true;
void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog, INT8 bAction)
{
FILE* DebugFile;
CHAR8 msg[1024];
CHAR8 buf[1024];
if (!gfLogsEnabled || pSoldier == nullptr)
if (!gfTurnBasedAI || !gfLogsEnabled || !doLog || pSoldier == nullptr)
return;
memset(buf, 0, 1024 * sizeof(char));
@@ -258,7 +337,7 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
strcat(msg, buf);
}
if (bAction >= AI_ACTION_NONE && bAction <= AI_ACTION_LAST)
if (bAction >= AI_ACTION_NONE && bAction < AI_ACTION_INVALID)
{
strcat(msg, " ");
strcat(msg, szAction[bAction]);
@@ -301,16 +380,19 @@ void DebugAI( INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction )
}
// also log to individual file for selected soldier
sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID.i);
if ((DebugFile = fopen(buf, "a+t")) != NULL)
if (pSoldier)
{
if (bMsgType == AI_MSG_START)
sprintf(buf, "Logs\\AI_Decisions [%d].txt", pSoldier->ubID.i);
if ((DebugFile = fopen(buf, "a+t")) != NULL)
{
if (bMsgType == AI_MSG_START)
{
fputs("\n", DebugFile);
}
fputs(msg, DebugFile);
fputs("\n", DebugFile);
fclose(DebugFile);
}
fputs(msg, DebugFile);
fputs("\n", DebugFile);
fclose(DebugFile);
}
}
@@ -354,6 +436,27 @@ void DebugQuestInfo(STR szOutput)
}
}
static INT16 ShouldActionStayInProgress(SOLDIERTYPE* pSoldier)
{
// this here should never happen, but it seems to (turns sometimes hang!)
if ((pSoldier->aiData.bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->pathing.bDesiredDirection != pSoldier->aiData.usActionData))
{
// don't try to pay any more APs for this, it was paid for once already!
pSoldier->pathing.bDesiredDirection = (INT8)pSoldier->aiData.usActionData; // turn to face direction in actionData
return(TRUE);
}
else if ((pSoldier->aiData.bAction == AI_ACTION_CHANGE_FACING) && (pSoldier->pathing.bDesiredDirection == pSoldier->aiData.usActionData))
{
return(FALSE);
}
else if (pSoldier->aiData.bAction == AI_ACTION_END_TURN || pSoldier->aiData.bAction == AI_ACTION_NONE)
{
return(FALSE);
}
// needs more time to complete action
return(TRUE);
}
BOOLEAN InitAI( void )
{
@@ -361,13 +464,6 @@ BOOLEAN InitAI( void )
FILE * DebugFile;
#endif
#ifdef _DEBUG
if (gfDisplayCoverValues)
{
//memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
#endif
//If we are not loading a saved game ( if we are, this has already been called )
if( !( gTacticalStatus.uiFlags & LOADING_SAVED_GAME ) )
{
@@ -399,10 +495,6 @@ BOOLEAN InitAI( void )
return( TRUE );
}
BOOLEAN AimingGun(SOLDIERTYPE *pSoldier)
{
return(FALSE);
}
void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named inappropriately
{
@@ -645,6 +737,9 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
{
// traversing offmap, ignore new situations
}
// FIXME: Disabled temporarily to prevent AI actions constantly being cancelled during normal turn based combat.
// Need to find out when this conditional is actually needed.
#if 0
else if ( pSoldier->ubQuoteRecord == 0 && !gTacticalStatus.fAutoBandageMode )
{
// don't force, don't want escorted mercs reacting to new opponents, etc.
@@ -658,6 +753,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
}
DecideAlertStatus( pSoldier );
}
#endif
else
{
if ( pSoldier->ubQuoteRecord )
@@ -665,6 +761,15 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
// make sure we're not using combat AI
pSoldier->aiData.bAlertStatus = STATUS_GREEN;
}
// Prevent AI deadlocking in case enemy is performing an action and player gets an interrupt.
// Without this, if player doesn't move any mercs, the AI soldier will wait until the deadlock is broken.
// By canceling the AI action, the AI can then reconsider actions.
//if (pSoldier->aiData.bAction == AI_ACTION_FIRE_GUN || pSoldier->aiData.bAction == AI_ACTION_KNIFE_MOVE || pSoldier->aiData.bAction == AI_ACTION_STEAL_MOVE || pSoldier->aiData.bAction == AI_ACTION_KNIFE_STAB)
{
DebugAI(AI_MSG_INFO, pSoldier, String("New Situation"));
CancelAIAction(pSoldier, FALSE);
}
pSoldier->aiData.bNewSituation = WAS_NEW_SITUATION;
}
}
@@ -674,7 +779,6 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
// might have been in 'was' state; no longer so...
pSoldier->aiData.bNewSituation = NOT_NEW_SITUATION;
}
#ifdef TESTAI
DebugMsg( TOPIC_JA2AI, DBG_LEVEL_3,String( ".... HANDLING AI FOR %d",pSoldier->ubID));
#endif
@@ -682,42 +786,79 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
/*********
Start of new overall AI system
********/
if (gfTurnBasedAI)
{
time_t tCurrentTime = time(0);
UINT32 uiShortDelay = 10;
UINT32 uiDelay = (UINT32)gGameExternalOptions.gubDeadLockDelay;
UINT32 uiTime = (UINT32)(tCurrentTime - gtTimeSinceMercAIStart);
BOOLEAN fKeyPressed = _KeyDown(ESC);
if ((uiTime > uiDelay || uiTime > uiShortDelay && fKeyPressed) && !gfUIInDeadlock)
//if ( ( GetJA2Clock() - gTacticalStatus.uiTimeSinceMercAIStart ) > ( (UINT32)gGameExternalOptions.gubDeadLockDelay * 1000 ) && !gfUIInDeadlock )
#if 0
{
// ATE: Display message that deadlock occured...
LiveMessage( "Breaking Deadlock" );
// added by Flugente: static pointers, used to break out of an endless circles
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 >= 600)
{
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, wszAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData);
DebugAI(AI_MSG_INFO, pSoldier, String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
DebugAI(AI_MSG_INFO, pSoldier, String("Last action was %s ", szAction[pSoldier->aiData.bLastAction]));
EndAIDeadlock();
//EndAIGuysTurn(pSoldier);
lastdecisioncount = 0;
return;
}
else
++lastdecisioncount;
}
else
{
pLastDecisionSoldier = pSoldier;
lastdecisioncount = 0;
}
}
#else
{
time_t tCurrentTime = time(0);
UINT32 uiShortDelay = 10;
UINT32 uiDelay = (UINT32)gGameExternalOptions.gubDeadLockDelay;
UINT32 uiTime = (UINT32)(tCurrentTime - gtTimeSinceMercAIStart);
BOOLEAN fKeyPressed = _KeyDown(ESC);
if ((uiTime > uiDelay || uiTime > uiShortDelay && fKeyPressed) && !gfUIInDeadlock)
//if ( ( GetJA2Clock() - gTacticalStatus.uiTimeSinceMercAIStart ) > ( (UINT32)gGameExternalOptions.gubDeadLockDelay * 1000 ) && !gfUIInDeadlock )
{
// ATE: Display message that deadlock occured...
LiveMessage( "Breaking Deadlock" );
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, wszAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData);
DebugAI(AI_MSG_INFO, pSoldier, String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
DebugAI(AI_MSG_INFO, pSoldier, String("Last action was %s ", szAction[pSoldier->aiData.bLastAction]));
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Aborting AI deadlock for [%d] %s %s data %d", pSoldier->ubID.i, pSoldier->GetName(), utf8_to_wstring(std::string(szAction[pSoldier->aiData.bAction])), pSoldier->aiData.usActionData);
DebugAI(String("Aborting AI deadlock for [%d] %s data %d", pSoldier->ubID, szAction[pSoldier->aiData.bAction], pSoldier->aiData.usActionData));
#ifdef JA2TESTVERSION
// display deadlock message
gfUIInDeadlock = TRUE;
DebugAI( String("DEADLOCK soldier %d action %s ABC %d", pSoldier->ubID.i, gzActionStr[pSoldier->aiData.bAction], gTacticalStatus.ubAttackBusyCount ) );
// display deadlock message
gfUIInDeadlock = TRUE;
DebugAI( String("DEADLOCK soldier %d action %s ABC %d", pSoldier->ubID.i, gzActionStr[pSoldier->aiData.bAction], gTacticalStatus.ubAttackBusyCount ) );
#else
// If we are in beta version, also report message!
// If we are in beta version, also report message!
#ifdef JA2BETAVERSION
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"Aborting AI deadlock for %d. Please sent DEBUG.TXT file and SAVE.", pSoldier->ubID.i );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_ERROR, L"Aborting AI deadlock for %d. Please sent DEBUG.TXT file and SAVE.", pSoldier->ubID.i );
#endif
// just abort
EndAIDeadlock();
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) )
{
return;
}
#endif
// just abort
EndAIDeadlock();
if ( !(pSoldier->flags.uiStatusFlags & SOLDIER_UNDERAICONTROL) )
{
return;
}
#endif
}
#endif
}
// We STILL do not want to issue new orders while an attack busy situation is going on. This can happen, for example,
@@ -727,7 +868,7 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
return;
}
if (pSoldier->aiData.bAction == AI_ACTION_NONE)
if (!pSoldier->aiData.bActionInProgress)
{
// being handled so turn off muzzle flash
if ( pSoldier->flags.fMuzzleFlash )
@@ -863,6 +1004,12 @@ void HandleSoldierAI( SOLDIERTYPE *pSoldier ) // FIXME - this function is named
{
ActionDone(pSoldier);
}
if (!ShouldActionStayInProgress(pSoldier))
{
DebugAI(AI_MSG_INFO, pSoldier, String("Action %s was stuck as being in progress. Canceling action", szAction[pSoldier->aiData.bAction]));
ActionDone(pSoldier);
}
}
/*********
End of new overall AI system
@@ -1327,7 +1474,7 @@ void FreeUpNPCFromRoofClimb(SOLDIERTYPE *pSoldier )
void ActionDone(SOLDIERTYPE *pSoldier)
{
// if an action is currently selected
if (pSoldier->aiData.bAction != AI_ACTION_NONE)
//if (pSoldier->aiData.bAction != AI_ACTION_NONE)
{
if (pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER)
{
@@ -1547,33 +1694,6 @@ INT16 ActionInProgress(SOLDIERTYPE *pSoldier)
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;
}
// yikes, this shouldn't occur! we should be trying to finish our move!
// pSoldier->flags.fNoAPToFinishMove = FALSE;
@@ -1606,6 +1726,8 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
#ifdef DEBUGBUSY
AINumMessage("Busy with action, skipping guy#",pSoldier->ubID);
#endif
ScreenMsg(FONT_MCOLOR_LTRED, MSG_INTERFACE, L"Busy with action %s, skipping guy [%d]", wszAction[pSoldier->aiData.bAction], pSoldier->ubID);
DebugAI(AI_MSG_INFO, pSoldier, String("Busy with action %s, skipping guy [%d]", wszAction[pSoldier->aiData.bAction], pSoldier->ubID));
// let it continue
return;
@@ -1731,43 +1853,42 @@ void TurnBasedHandleNPCAI(SOLDIERTYPE *pSoldier)
{
pSoldier->aiData.bAction = AI_ACTION_NONE;
}
}
// if he chose to continue doing nothing
if (pSoldier->aiData.bAction == AI_ACTION_NONE)
{
// if he chose to continue doing nothing
if (pSoldier->aiData.bAction == AI_ACTION_NONE)
{
#ifdef RECORDNET
fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID);
fprintf(NetDebugFile,"\tMOVED BECOMING TRUE: Chose to do nothing, guynum %d\n",pSoldier->ubID);
#endif
DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned");
NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE
return;
}
// to get here, we MUST have an action selected, but not in progress...
// see if we can afford to do this action
if (IsActionAffordable(pSoldier))
{
NPCDoesAct(pSoldier);
DebugMsg (TOPIC_JA2AI,DBG_LEVEL_3,"NPC has no action assigned");
NPCDoesNothing(pSoldier); // sets pSoldier->moved to TRUE
return;
}
// to get here, we MUST have an action selected, but not in progress...
// see if we can afford to do this action
if (IsActionAffordable(pSoldier))
{
NPCDoesAct(pSoldier);
// perform the chosen action
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
// perform the chosen action
pSoldier->aiData.bActionInProgress = ExecuteAction(pSoldier); // if started, mark us as busy
if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
{
// turn based... abort this guy's turn
EndAIGuysTurn( pSoldier );
lastdecisioncount = 0;
}
}
else
if ( !pSoldier->aiData.bActionInProgress && !TileIsOutOfBounds(pSoldier->sAbsoluteFinalDestination))
{
#ifdef DEBUGDECISIONS
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID);
#endif
HaltMoveForSoldierOutOfPoints( pSoldier);
return;
// turn based... abort this guy's turn
EndAIGuysTurn( pSoldier );
}
}
else
{
#ifdef DEBUGDECISIONS
AINumMessage("HandleManAI - Not enough APs, skipping guy#",pSoldier->ubID);
#endif
HaltMoveForSoldierOutOfPoints( pSoldier);
return;
}
}
@@ -1799,6 +1920,7 @@ void RefreshAI(SOLDIERTYPE *pSoldier)
if ((pSoldier->aiData.bAlertStatus == STATUS_BLACK) || (pSoldier->aiData.bAlertStatus == STATUS_RED))
{
// always freshly rethink things at start of his turn
//CancelAIAction(pSoldier, FALSE);
pSoldier->aiData.bNewSituation = IS_NEW_SITUATION;
}
else
@@ -2281,14 +2403,24 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
usHandItem = GetAttachedGrenadeLauncher(&pSoldier->inv[HANDPOS]);
iRetCode = HandleItem( pSoldier, pSoldier->aiData.usActionData, pSoldier->bTargetLevel, usHandItem, FALSE );
// If AI cannot shoot because of lack of APs, attempt to try again with lower aim.
// Usually happens when they have to turn before shooting. Without this, the game would cancel soldier's whole turn
if (iRetCode == ITEM_HANDLE_NOAPS && pSoldier->aiData.bAimTime > 0)
{
do
{
pSoldier->aiData.bAimTime -= 1;
iRetCode = HandleItem(pSoldier, pSoldier->aiData.usActionData, pSoldier->bTargetLevel, usHandItem, FALSE);
} while (iRetCode == ITEM_HANDLE_NOAPS && pSoldier->aiData.bAimTime > 0);
}
if ( iRetCode != ITEM_HANDLE_OK)
{
if ( iRetCode != ITEM_HANDLE_BROKEN ) // if the item broke, this is 'legal' and doesn't need reporting
{
DebugAI( String( "AI %d got error code %ld from HandleItem, doing action %d, has %d APs... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction, pSoldier->bActionPoints ) );
DebugAI(AI_MSG_INFO, pSoldier, String( "AI %d got error code %ld from HandleItem, doing action %d, has %d APs... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction, pSoldier->bActionPoints ) );
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"AI %d got error code %ld from HandleItem, doing action %d... aborting deadlock!", pSoldier->ubID, iRetCode, pSoldier->aiData.bAction );
}
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction: !ITEM_HANDLE_OK"));
CancelAIAction( pSoldier, FORCE);
#ifdef TESTAICONTROL
if (gfTurnBasedAI)
@@ -2612,7 +2744,30 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
case AI_ACTION_JUMP_WINDOW:
{
pSoldier->BeginSoldierClimbWindow();
pSoldier->BeginSoldierJumpWindowAI();
if ( gfTurnBasedAI )
{
//if (pSoldier->bActionPoints >= GetAPsToJumpThroughWindows(pSoldier, FALSE) + GetAPsToLook(pSoldier))
if ( pSoldier->bActionPoints >= GetAPsToLook(pSoldier) )
{
pSoldier->aiData.bNextAction = AI_ACTION_CHANGE_FACING;
INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
if ( !TileIsOutOfBounds(sClosestOpponent) )
pSoldier->aiData.usNextActionData = AIDirection(pSoldier->sGridNo, sClosestOpponent);
else
pSoldier->aiData.usNextActionData = PreRandom(8);
}
else
{
pSoldier->aiData.bNextAction = AI_ACTION_END_TURN;
pSoldier->aiData.usNextActionData = 0;
}
}
else
{
pSoldier->aiData.bNextAction = AI_ACTION_WAIT;
pSoldier->aiData.usNextActionData = REALTIME_AI_DELAY / 10;
}
ActionDone( pSoldier );
}
break;
@@ -2675,7 +2830,73 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
}
break;
default:
case AI_ACTION_DRINK_CANTEEN:
DrinkFromInventory(pSoldier);
ActionDone(pSoldier);
break;
case AI_ACTION_HANDLE_ITEM:
iRetCode = HandleItem(pSoldier, pSoldier->aiData.usActionData, pSoldier->pathing.bLevel, pSoldier->inv[HANDPOS].usItem, FALSE);
if ( iRetCode != ITEM_HANDLE_OK )
{
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_HANDLE_ITEM): HandleItem error code %d", iRetCode));
CancelAIAction(pSoldier, FORCE);
EndAIGuysTurn(pSoldier);
}
break;
case AI_ACTION_PLANT_BOMB:
if ( pSoldier->inv[HANDPOS].exists() &&
(Item[pSoldier->inv[HANDPOS].usItem].usItemClass & IC_BOMB) )
{
OBJECTTYPE bombobj;
INT32 sSpot = pSoldier->sGridNo;
if ( !TileIsOutOfBounds(sSpot) &&
pSoldier->inv[HANDPOS].MoveThisObjectTo(bombobj, 1) == 0 )
{
bombobj.fFlags |= OBJECT_ARMED_BOMB;
bombobj[0]->data.misc.bDetonatorType = BOMB_TIMED;
bombobj[0]->data.misc.usBombItem = bombobj.usItem;
//bombobj[0]->data.misc.ubBombOwner = pSoldier->ubID + 2;
bombobj[0]->data.misc.ubBombOwner = 1;
bombobj[0]->data.misc.bDelay = 1 + Random(2);
//pSoldier->inv[HANDPOS][0]->data.bTrap = EffectiveExplosive(pSoldier) / 20 + EffectiveExpLevel(pSoldier, TRUE) / 2;
pSoldier->inv[HANDPOS][0]->data.bTrap = 6 + SoldierDifficultyLevel(pSoldier);
AddItemToPool(sSpot, &bombobj, INVISIBLE, pSoldier->pathing.bLevel, WORLD_ITEM_ARMED_BOMB, 0);
NotifySoldiersToLookforItems();
DeductPoints(pSoldier, APBPConstants[AP_INVENTORY_ARM] + APBPConstants[AP_DROP_BOMB], APBPConstants[BP_INVENTORY_ARM] + APBPConstants[BP_DROP_BOMB]);
if ( gAnimControl[pSoldier->usAnimState].ubHeight == ANIM_STAND )
{
pSoldier->EVENT_InitNewSoldierAnim(DROP_ITEM, 0, FALSE);
}
else if ( gAnimControl[pSoldier->usAnimState].ubHeight == ANIM_CROUCH )
{
pSoldier->EVENT_InitNewSoldierAnim(CUTTING_FENCE, 0, FALSE);
}
if ( pSoldier->bVisible != -1 )
{
PlayJA2Sample(THROW_IMPACT_2, RATE_11025, SoundVolume(MIDVOLUME, pSoldier->sGridNo), 1, SoundDir(pSoldier->sGridNo));
}
ActionDone(pSoldier);
}
else
{
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_PLANT_BOMB): failed to move object"));
CancelAIAction(pSoldier, FORCE);
EndAIGuysTurn(pSoldier);
}
}
else
{
DebugAI(AI_MSG_INFO, pSoldier, String("CancelAIAction (AI_ACTION_PLANT_BOMB): failed to find bomb in hand"));
CancelAIAction(pSoldier, FORCE);
EndAIGuysTurn(pSoldier);
}
break;
default:
#ifdef BETAVERSION
NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction);
#endif
+353 -24
View File
@@ -27,6 +27,7 @@
#include "SmokeEffects.h" // sevenfm
#include "GameInitOptionsScreen.h"
#include "Structure Wrap.h"
//////////////////////////////////////////////////////////////////////////////
// SANDRO - In this file, all APBPConstants[AP_CROUCH] and APBPConstants[AP_PRONE] were changed to GetAPsCrouch() and GetAPsProne()
@@ -134,18 +135,17 @@ INT8 OKToAttack(SOLDIERTYPE * pSoldier, int target)
BOOLEAN ConsiderProne( SOLDIERTYPE * pSoldier )
{
INT32 sOpponentGridNo;
INT32 sOpponentGridNo;
INT8 bOpponentLevel;
INT32 iRange;
INT32 iRangeInCellCoords;
if (pSoldier->aiData.bAIMorale >= MORALE_NORMAL)
{
return( FALSE );
}
// We don't want to go prone if there is a nearby enemy
ClosestKnownOpponent( pSoldier, &sOpponentGridNo, &bOpponentLevel );
iRange = PythSpacesAway( pSoldier->sGridNo, sOpponentGridNo );
if (iRange > 10)
ClosestKnownOpponent( pSoldier, &sOpponentGridNo, &bOpponentLevel, nullptr, &iRangeInCellCoords);
if ( iRangeInCellCoords > 10*CELL_X_SIZE)
{
return( TRUE );
}
@@ -397,7 +397,11 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// sevenfm: movement mode tweaks
if (gGameExternalOptions.fAIMovementMode)
{
INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL);
INT32 distanceToThreat;
const INT32 sClosestThreat = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
const auto mediumRange = TACTICAL_RANGE_CELL_COORDS / 2;
const auto close = TACTICAL_RANGE_CELL_COORDS / 4;
const auto reallyClose = TACTICAL_RANGE_CELL_COORDS / 8;
// use walking mode if no enemy known
if (pSoldier->aiData.bAlertStatus < STATUS_RED &&
@@ -418,11 +422,12 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
if (IS_MERC_BODY_TYPE(pSoldier) &&
pSoldier->aiData.bAlertStatus >= STATUS_YELLOW &&
!InWaterGasOrSmoke(pSoldier, pSoldier->sGridNo) &&
!(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) &&
!BOXER(pSoldier) &&
!TileIsOutOfBounds(sClosestThreat) &&
(pSoldier->bTeam == ENEMY_TEAM || pSoldier->bTeam == MILITIA_TEAM))
{
INT16 sDistanceVisible = VISION_RANGE;
const auto beyondVisionRange = (CELL_X_SIZE * 3 * sDistanceVisible) / 2;
INT32 iRCD = RangeChangeDesire(pSoldier);
// use running when in light at night
@@ -445,7 +450,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
!GuySawEnemy(pSoldier) &&
(NightTime() || gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH) &&
CountNearbyFriends(pSoldier, pSoldier->sGridNo, TACTICAL_RANGE / 4) < 3 &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 &&
distanceToThreat < beyondVisionRange &&
CountFriendsBlack(pSoldier) == 0 &&
bAction == AI_ACTION_SEEK_OPPONENT)
{
@@ -454,7 +459,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// use swatting for taking cover
if (pSoldier->aiData.bAlertStatus >= STATUS_RED &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8 &&
distanceToThreat > reallyClose &&
(pSoldier->aiData.bUnderFire && iRCD < 4 ||
pSoldier->aiData.bShock > 2 * iRCD ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE) &&
@@ -466,9 +471,9 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
// use SWATTING when under fire
if (pSoldier->aiData.bAlertStatus >= STATUS_RED &&
(pSoldier->aiData.bShock > iRCD && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 2 ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) < 3 * sDistanceVisible / 2 &&
(pSoldier->aiData.bShock > iRCD && distanceToThreat > mediumRange ||
pSoldier->aiData.bShock > 0 && gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE && distanceToThreat > close) &&
distanceToThreat < beyondVisionRange &&
gAnimControl[pSoldier->usAnimState].ubEndHeight <= ANIM_CROUCH &&
!pSoldier->aiData.bLastAttackHit &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
@@ -485,7 +490,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
(pSoldier->aiData.bOrders == SNIPER ||
pSoldier->aiData.bOrders == STATIONARY ||
(GuySawEnemy(pSoldier) || pSoldier->aiData.bShock > 0) && iRCD < 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 &&
distanceToThreat > close &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND ||
@@ -501,7 +506,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
(pSoldier->aiData.bOrders == SNIPER ||
pSoldier->aiData.bOrders == STATIONARY ||
pSoldier->aiData.bShock > 0 && iRCD < 4) &&
PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 4 &&
distanceToThreat > close &&
(bAction == AI_ACTION_SEEK_OPPONENT ||
bAction == AI_ACTION_GET_CLOSER ||
bAction == AI_ACTION_SEEK_FRIEND ||
@@ -515,7 +520,7 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
if (!pSoldier->aiData.bUnderFire &&
bAction == AI_ACTION_TAKE_COVER &&
pSoldier->bInitialActionPoints > APBPConstants[AP_MINIMUM] &&
(!InARoom(pSoldier->sGridNo, NULL) || PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > sDistanceVisible * 2) &&
(!InARoom(pSoldier->sGridNo, NULL) || distanceToThreat > sDistanceVisible * 20) &&
pSoldier->aiData.bAIMorale >= MORALE_NORMAL &&
pSoldier->bBreath > 25 &&
pSoldier->pathing.bLevel == 0 &&
@@ -539,14 +544,14 @@ UINT16 DetermineMovementMode( SOLDIERTYPE * pSoldier, INT8 bAction )
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_CROUCH)
{
if (WeaponReady(pSoldier) && !pSoldier->aiData.bUnderFire && pSoldier->aiData.bAlertStatus == STATUS_BLACK ||
pSoldier->aiData.bUnderFire && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8)
pSoldier->aiData.bUnderFire && distanceToThreat > reallyClose )
return SWATTING;
else
return RUNNING;
}
else if (gAnimControl[pSoldier->usAnimState].ubEndHeight == ANIM_PRONE)
{
if (pSoldier->aiData.bUnderFire && !pSoldier->aiData.bLastAttackHit && PythSpacesAway(pSoldier->sGridNo, sClosestThreat) > (INT16)TACTICAL_RANGE / 8)
if (pSoldier->aiData.bUnderFire && !pSoldier->aiData.bLastAttackHit && distanceToThreat > reallyClose )
return SWATTING;
else
return RUNNING;
@@ -808,6 +813,18 @@ BOOLEAN IsActionAffordable(SOLDIERTYPE *pSoldier, INT8 bAction)
bMinPointsNeeded = 20; // TODO
break;
case AI_ACTION_DRINK_CANTEEN:
bMinPointsNeeded = APBPConstants[AP_DRINK];
break;
case AI_ACTION_HANDLE_ITEM:
bMinPointsNeeded = 0;
break;
case AI_ACTION_PLANT_BOMB:
bMinPointsNeeded = APBPConstants[AP_INVENTORY_ARM] + APBPConstants[AP_DROP_BOMB];
break;
default:
#ifdef BETAVERSION
//NumMessage("AffordableAction - Illegal action type = ",pSoldier->aiData.bAction);
@@ -1379,7 +1396,7 @@ INT32 ClosestReachableDisturbance(SOLDIERTYPE *pSoldier, BOOLEAN * pfChangeLevel
}
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID * pubOpponentID)
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID * pubOpponentID, INT32 * distanceInCellCoords)
{
INT32 *psLastLoc,sGridNo, sClosestOpponent = NOWHERE;
UINT32 uiLoop;
@@ -1475,7 +1492,7 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
if (sClosestOpponent == NOWHERE ||
iRange < iClosestRange ||
pClosestOpponent && !pClosestOpponent->IsZombie() && !(pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)
pClosestOpponent && !pClosestOpponent->IsZombie() && !BOXER(pSoldier) && pClosestOpponent->stats.bLife < OKLIFE && pOpponent->stats.bLife >= OKLIFE)
{
iClosestRange = iRange;
sClosestOpponent = sGridNo;
@@ -1503,6 +1520,10 @@ INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLev
{
*pubOpponentID = pClosestOpponent->ubID;
}
if ( distanceInCellCoords )
{
*distanceInCellCoords = iClosestRange;
}
return( sClosestOpponent );
}
@@ -2232,6 +2253,31 @@ INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier )
return( sMinDist );
}
BOOLEAN InSmoke(SOLDIERTYPE* pSoldier, INT32 sGridNo)
{
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_SIGNAL_SMOKE | MAPELEMENT_EXT_DEBRIS_SMOKE | MAPELEMENT_EXT_FIRERETARDANT_SMOKE) )
return TRUE;
return FALSE;
}
BOOLEAN InTearGas(SOLDIERTYPE* pSoldier, INT32 sGridNo)
{
if ( (gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_TEARGAS))
return TRUE;
return FALSE;
}
BOOLEAN InMustardGas(SOLDIERTYPE* pSoldier, INT32 sGridNo)
{
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_BURNABLEGAS | MAPELEMENT_EXT_CREATUREGAS | MAPELEMENT_EXT_MUSTARDGAS) )
return TRUE;
return FALSE;
}
BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
{
if (WaterTooDeepForAttacks( sGridNo, pSoldier->pathing.bLevel ))
@@ -2250,6 +2296,12 @@ BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
{
// Armed vehicles and robots do not care about gas or smoke
if (ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier))
{
return FALSE;
}
// smoke
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE | MAPELEMENT_EXT_SIGNAL_SMOKE | MAPELEMENT_EXT_DEBRIS_SMOKE | MAPELEMENT_EXT_FIRERETARDANT_SMOKE ) )
return TRUE;
@@ -2297,6 +2349,12 @@ BOOLEAN InGas(SOLDIERTYPE *pSoldier, INT32 sGridNo)
if (TileIsOutOfBounds(sGridNo))
return FALSE;
// Armed vehicles and robots do not care about gas or smoke
if (ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier))
{
return FALSE;
}
if (InGasSpot(pSoldier, sGridNo, pSoldier->pathing.bLevel))
{
return TRUE;
@@ -2655,7 +2713,7 @@ INT32 CalcManThreatValue( SOLDIERTYPE *pEnemy, INT32 sMyGrid, UINT8 ubReduceForC
}
// in boxing mode, let only a boxer be considered a threat.
if ( (gTacticalStatus.bBoxingState == BOXING) && !(pEnemy->flags.uiStatusFlags & SOLDIER_BOXER) )
if ( (gTacticalStatus.bBoxingState == BOXING) && !BOXER(pEnemy) )
{
iThreatValue = -999;
return( iThreatValue );
@@ -3896,8 +3954,6 @@ INT8 CalcMoraleNew(SOLDIERTYPE *pSoldier)
bMoraleCategory ++;
}
INT32 sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL);
// if last attack of this soldier hit enemy - increase morale
if( pSoldier->aiData.bLastAttackHit )
{
@@ -4576,6 +4632,61 @@ UINT8 RedSmokeDanger(INT32 sGridNo, INT8 bLevel)
return ubDangerPercent;
}
BOOLEAN FindClosestVisibleSmoke(SOLDIERTYPE* pSoldier, INT32& sSpot, INT8& bLevel, BOOLEAN fOnlyGas)
{
CHECKF(pSoldier);
INT32 sDist;
INT32 sClosestDist = INT32_MAX;
INT32 sCheckSpot;
INT8 bCheckLevel;
sSpot = NOWHERE;
bLevel = 0;
//loop through all smoke effects and find closest visible
for ( UINT32 uiCnt = 0; uiCnt < guiNumSmokeEffects; uiCnt++ )
{
if ( gSmokeEffectData[uiCnt].fAllocated &&
!TileIsOutOfBounds(gSmokeEffectData[uiCnt].sGridNo) )
{
// ignore smoke if not dangerous
if ( fOnlyGas &&
gSmokeEffectData[uiCnt].bType != TEARGAS_SMOKE_EFFECT &&
gSmokeEffectData[uiCnt].bType != MUSTARDGAS_SMOKE_EFFECT &&
gSmokeEffectData[uiCnt].bType != CREATURE_SMOKE_EFFECT )
{
continue;
}
sCheckSpot = gSmokeEffectData[uiCnt].sGridNo;
if ( gSmokeEffectData[uiCnt].bFlags & SMOKE_EFFECT_ON_ROOF )
bCheckLevel = 1;
else
bCheckLevel = 0;
sDist = PythSpacesAway(sCheckSpot, pSoldier->sGridNo);
if ( sDist < DAY_VISION_RANGE &&
SoldierToVirtualSoldierLineOfSightTest(pSoldier, sCheckSpot, bCheckLevel, ANIM_PRONE, 0, CALC_FROM_ALL_DIRS) &&
(sSpot == NOWHERE || sDist < sClosestDist) )
{
sClosestDist = sDist;
sSpot = sCheckSpot;
bLevel = bCheckLevel;
}
}
}
if ( !TileIsOutOfBounds(sSpot) )
{
return TRUE;
}
return FALSE;
}
// check if artillery strike was ordered by any team
BOOLEAN CheckArtilleryStrike(void)
{
@@ -4784,7 +4895,7 @@ BOOLEAN ValidOpponent(SOLDIERTYPE* pSoldier, SOLDIERTYPE* pOpponent)
pSoldier->bSide == pOpponent->bSide ||
pSoldier->aiData.bAttitude == ATTACKSLAYONLY && pOpponent->ubProfile != SLAY ||
(pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0) ||
gTacticalStatus.bBoxingState == BOXING && (pSoldier->flags.uiStatusFlags & SOLDIER_BOXER) && !(pOpponent->flags.uiStatusFlags & SOLDIER_BOXER) ||
gTacticalStatus.bBoxingState == BOXING && BOXER(pSoldier) && !BOXER(pOpponent) ||
pOpponent->ubBodyType == CROW)
{
return FALSE;
@@ -4893,7 +5004,7 @@ BOOLEAN SoldierAI(SOLDIERTYPE *pSoldier)
if (!IS_MERC_BODY_TYPE(pSoldier) ||
pSoldier->aiData.bNeutral ||
fCivilian ||
pSoldier->flags.uiStatusFlags & SOLDIER_BOXER ||
BOXER(pSoldier) ||
ARMED_VEHICLE(pSoldier) ||
pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ||
AM_A_ROBOT(pSoldier) ||
@@ -6229,3 +6340,221 @@ BOOLEAN CheckSuppressionDirection(SOLDIERTYPE *pSoldier, INT32 sTargetGridNo, IN
return TRUE;
}
UINT8 CountKnownEnemies(SOLDIERTYPE* pSoldier, INT32 sSpot, INT16 sDistance, INT8 bLevel)
{
CHECKF(pSoldier);
SOLDIERTYPE* pOpponent;
INT32 sThreatLoc;
INT8 bThreatLevel;
UINT8 ubNum = 0;
// loop through all the enemies
for ( UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop )
{
pOpponent = MercSlots[uiLoop];
// if this merc is inactive, at base, on assignment, dead, unconscious
if ( !pOpponent || pOpponent->stats.bLife < OKLIFE )
{
continue;
}
if ( !ValidOpponent(pSoldier, pOpponent) )
{
continue;
}
// check knowledge
if ( Knowledge(pSoldier, pOpponent->ubID) == NOT_HEARD_OR_SEEN )
{
continue;
}
sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID);
bThreatLevel = KnownLevel(pSoldier, pOpponent->ubID);
if ( TileIsOutOfBounds(sThreatLoc) )
{
continue;
}
if ( PythSpacesAway(sSpot, sThreatLoc) > sDistance )
{
continue;
}
if ( bLevel >= 0 && bThreatLevel != bLevel )
{
continue;
}
ubNum++;
}
return ubNum;
}
UINT8 CountKnownEnemiesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom)
{
CHECKF(pSoldier);
UINT8 ubNum = 0;
for ( UINT32 uiLoop = 0; uiLoop < guiNumMercSlots; ++uiLoop )
{
SOLDIERTYPE* pOpponent = MercSlots[uiLoop];
// if this merc is inactive, at base, on assignment, dead, unconscious
if ( !pOpponent || pOpponent->stats.bLife < OKLIFE )
{
continue;
}
if ( !ValidOpponent(pSoldier, pOpponent) )
{
continue;
}
// check public knowledge
if ( Knowledge(pSoldier, pOpponent->ubID) == NOT_HEARD_OR_SEEN )
{
continue;
}
INT32 sThreatLoc = KnownLocation(pSoldier, pOpponent->ubID);
if ( TileIsOutOfBounds(sThreatLoc) )
{
continue;
}
// check room
UINT16 usRoomNo;
if ( !InARoom(sThreatLoc, &usRoomNo) )
{
continue;
}
if ( usRoomNo != usRoom )
{
continue;
}
ubNum++;
}
return ubNum;
}
UINT8 CountFriendsInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom)
{
CHECKF(pSoldier);
SOLDIERTYPE* pFriend;
UINT8 ubFriendCount = 0;
UINT16 usRoomNo;
// Run through each friendly.
for ( SoldierID iCounter = gTacticalStatus.Team[pSoldier->bTeam].bFirstID; iCounter <= gTacticalStatus.Team[pSoldier->bTeam].bLastID; ++iCounter )
{
pFriend = iCounter;
if ( pFriend &&
pFriend != pSoldier &&
pFriend->bActive &&
pFriend->stats.bLife >= OKLIFE &&
InARoom(pFriend->sGridNo, &usRoomNo) &&
usRoomNo == usRoom )
{
ubFriendCount++;
}
}
return ubFriendCount;
}
INT32 CountCorpsesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoomNo, INT8 bLevel)
{
CHECKF(pSoldier);
ROTTING_CORPSE* pCorpse;
INT32 iCount = 0;
for ( INT32 cnt = 0; cnt < giNumRottingCorpse; ++cnt )
{
pCorpse = &(gRottingCorpse[cnt]);
if ( pCorpse &&
pCorpse->fActivated &&
pCorpse->def.ubType < ROTTING_STAGE2 &&
pCorpse->def.ubBodyType <= REGFEMALE &&
pCorpse->def.ubAIWarningValue > 0 &&
pCorpse->def.bLevel == bLevel &&
!TileIsOutOfBounds(pCorpse->def.sGridNo) &&
RoomNo(pCorpse->def.sGridNo) == usRoomNo &&
(pSoldier->bTeam == ENEMY_TEAM && CorpseEnemyTeam(pCorpse) || pSoldier->bTeam == MILITIA_TEAM && CorpseMilitiaTeam(pCorpse) || pSoldier->bTeam == CIV_TEAM && !pSoldier->aiData.bNeutral) )
{
iCount++;
}
}
return iCount;
}
BOOLEAN FindFenceAroundSpot(INT32 sSpot)
{
if ( TileIsOutOfBounds(sSpot) )
{
return FALSE;
}
INT32 sTempSpot;
// check adjacent locations
for ( UINT8 ubDirection = 0; ubDirection < NUM_WORLD_DIRECTIONS; ubDirection++ )
{
sTempSpot = NewGridNo(sSpot, DirectionInc(ubDirection));
if ( sTempSpot != sSpot && IsCuttableWireFenceAtGridNo(sTempSpot) )
{
return TRUE;
}
}
return FALSE;
}
BOOLEAN SameRoom(INT32 sSpot1, INT32 sSpot2)
{
if ( RoomNo(sSpot1) == RoomNo(sSpot2) && sSpot1 != NO_ROOM )
return TRUE;
return FALSE;
}
UINT16 RoomNo(INT32 sSpot)
{
if ( TileIsOutOfBounds(sSpot) )
return NO_ROOM;
return gusWorldRoomInfo[sSpot];
}
BOOLEAN CheckWindow(INT32 sSpot, UINT8 ubDirection, BOOLEAN fAllowClosed)
{
CHECKF(!TileIsOutOfBounds(sSpot));
// find window spot
INT32 sWindowSpot = sSpot;
if ( ubDirection == NORTH || ubDirection == WEST )
sWindowSpot = NewGridNo(sSpot, (UINT16)DirectionInc(ubDirection));
//if (IsJumpableWindowPresentAtGridNo(sWindowSpot, ubDirection, gGameExternalOptions.fCanJumpThroughClosedWindows))
if ( IsJumpableWindowPresentAtGridNo(sWindowSpot, ubDirection, fAllowClosed) )
{
return TRUE;
}
return FALSE;
}
+685 -1
View File
@@ -2207,7 +2207,7 @@ INT32 EstimateShotDamage(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT16 ub
// check for ceramic plates; these do affect monster spit
for (attachmentList::iterator iter = pOpponent->inv[VESTPOS][0]->attachments.begin(); iter != pOpponent->inv[VESTPOS][0]->attachments.end(); ++iter) {
if ( iter->exists() && Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 )
if (Item[iter->usItem].usItemClass == IC_ARMOUR && (*iter)[0]->data.objectStatus > 0 && iter->exists() )
{
iTorsoProt += (INT32) Armour[Item[iter->usItem].ubClassIndex].ubProtection *
(INT32) (*iter)[0]->data.objectStatus / 100;
@@ -4234,3 +4234,687 @@ void CheckTossGrenadeAt(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestThrow, INT32 sTa
pSoldier->bWeaponMode = WM_NORMAL;
}
static void CalculatePossibleShots(SOLDIERTYPE* pSoldier, INT8 gun, std::vector<ATTACKTYPE> &possibleShots)
{
UINT32 uiLoop;
INT32 iAttackValue, iThreatValue, iHitRate, iBestHitRate, iPercentBetter, iEstDamage, iTrueLastTarget;
UINT16 usTrueState, usTurningCost, usRaiseGunCost;
INT16 sMinAPcost;
INT16 sRawAPCost;
INT16 sAimAPCost;
INT16 sBestAPcost;
INT16 sChanceToHit;
INT16 sAimTime;
INT16 sBestAimTime;
INT16 sMaxPossibleAimTime;
UINT8 ubChanceToGetThrough;
UINT8 ubBestChanceToGetThrough;
UINT8 ubFriendlyFireChance;
UINT8 ubBestFriendlyFireChance;
INT16 sBestChanceToHit;
INT16 sStanceAPcost;
BOOLEAN fAddingTurningCost, fAddingRaiseGunCost;
UINT8 ubStance, ubBestStance, ubChanceToReallyHit;
INT8 bScopeMode;
SOLDIERTYPE* pOpponent;
// sevenfm:
BOOLEAN fSuppression = FALSE;
BOOLEAN fReturnFire = FALSE;
INT32 sTarget = NOWHERE;
INT8 bLevel;
INT8 bKnowledge;
INT8 bPersonalKnowledge;
INT8 bPublicKnowledge;
DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "CalcBestShot");
sBestChanceToHit = sBestAimTime = sChanceToHit = ubBestChanceToGetThrough = ubBestFriendlyFireChance = ubChanceToReallyHit = 0;
// sevenfm: set attacking hand and target
pSoldier->ubAttackingHand = HANDPOS;
pSoldier->ubTargetID = NOBODY;
pSoldier->usAttackingWeapon = pSoldier->inv[HANDPOS].usItem;
pSoldier->bWeaponMode = WM_NORMAL;
std::map<INT8, OBJECTTYPE*> ObjList;
GetScopeLists(pSoldier, &pSoldier->inv[HANDPOS], ObjList);
pSoldier->bScopeMode = USE_BEST_SCOPE;
pSoldier->bDoBurst = 0;
pSoldier->bDoAutofire = 0;
// determine which attack against which target has the greatest attack value
for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++)
{
ATTACKTYPE pBestShot;
pBestShot.ubPossible = FALSE;
pBestShot.ubChanceToReallyHit = 0;
pBestShot.iAttackValue = 0;
pBestShot.ubOpponent = NOBODY;
pBestShot.ubFriendlyFireChance = 0;
pBestShot.bWeaponIn = gun;
pOpponent = MercSlots[uiLoop];
fSuppression = FALSE;
fReturnFire = FALSE;
// if this merc is inactive, at base, on assignment, or dead
if (!pOpponent || !pOpponent->stats.bLife)
continue; // next merc
if (!ValidOpponent(pSoldier, pOpponent))
{
continue;
}
// determine return fire
if (pSoldier->aiData.bUnderFire &&
!pSoldier->bBlindedCounter &&
pSoldier->ubPreviousAttackerID == pOpponent->ubID)
{
fReturnFire = TRUE;
}
bKnowledge = Knowledge(pSoldier, pOpponent->ubID);
bPersonalKnowledge = PersonalKnowledge(pSoldier, pOpponent->ubID);
bPublicKnowledge = PublicKnowledge(pSoldier->bTeam, pOpponent->ubID);
// check knowledge
if (bKnowledge != SEEN_CURRENTLY &&
bKnowledge != SEEN_THIS_TURN &&
bKnowledge != SEEN_LAST_TURN &&
bKnowledge != HEARD_THIS_TURN &&
bKnowledge != HEARD_LAST_TURN &&
!((bKnowledge == SEEN_2_TURNS_AGO || bKnowledge == SEEN_3_TURNS_AGO || bKnowledge == HEARD_2_TURNS_AGO) && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG))
{
continue; // next opponent
}
// sevenfm: blind soldier can only attack seen/heard personally
if (pSoldier->bBlindedCounter > 0 &&
bPersonalKnowledge != SEEN_THIS_TURN &&
bPersonalKnowledge != HEARD_THIS_TURN)
{
continue; // next opponent
}
// sevenfm: determine if we shoot on unseen target for suppression
if (bPersonalKnowledge != SEEN_CURRENTLY &&
bPublicKnowledge != SEEN_CURRENTLY &&
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS))
{
fSuppression = TRUE;
}
// sevenfm: shooting at unseen opponents is optional
if (fSuppression && !gGameExternalOptions.fAIShootUnseen)
{
continue; // next opponent
}
// determine enemy location
if (fSuppression)
{
// personal/public knowledge
sTarget = KnownLocation(pSoldier, pOpponent->ubID);
bLevel = KnownLevel(pSoldier, pOpponent->ubID);
// try to randomize location
sTarget = RandomizeLocation(sTarget, bLevel, 1, pSoldier);
}
else
{
// we know exact enemy location
sTarget = pOpponent->sGridNo;
bLevel = pOpponent->pathing.bLevel;
}
// safety check
if (TileIsOutOfBounds(sTarget))
{
continue;
}
// skip if we can see location and location is empty
if (SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_PRONE, TRUE, CALC_FROM_ALL_DIRS) &&
WhoIsThere2(sTarget, bLevel) == NOBODY)
{
continue;
}
// no fire on unseen opponents with throwing knives
if ((Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) &&
bPersonalKnowledge != SEEN_CURRENTLY &&
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS))
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS))
{
continue;
}
// sevenfm: only enemy team can use blind suppression fire
if (fSuppression &&
pSoldier->bTeam != ENEMY_TEAM)
{
continue;
}
// sevenfm: only try to suppress alive and conscious human targets
if (fSuppression &&
(pOpponent->stats.bLife < OKLIFE ||
pOpponent->bCollapsed && pOpponent->bBreath == 0 ||
pOpponent->IsCowering() ||
pOpponent->IsZombie() ||
!IS_MERC_BODY_TYPE(pOpponent)))
{
continue;
}
// shoot through wall check
if (bPersonalKnowledge != SEEN_CURRENTLY &&
//!SoldierToSoldierLineOfSightTest(pSoldier, pOpponent, TRUE, CALC_FROM_ALL_DIRS) &&
!LOS_Raised(pSoldier, pOpponent, CALC_FROM_ALL_DIRS) &&
!SoldierToVirtualSoldierLineOfSightTest(pSoldier, sTarget, bLevel, ANIM_STAND, TRUE, NO_DISTANCE_LIMIT) &&
!LocationToLocationLineOfSightTest(pSoldier->sGridNo, pSoldier->pathing.bLevel, sTarget, bLevel, TRUE, NO_DISTANCE_LIMIT) &&
!fReturnFire &&
!(InARoom(sTarget, NULL) && bLevel == 0 && Weapon[pSoldier->usAttackingWeapon].ubWeaponType == GUN_LMG) && // bPublicKnowledge == SEEN_CURRENTLY &&
!(InARoom(sTarget, NULL) && bLevel == 0 && TeamPercentKilled(pSoldier->bTeam) > (100 - 20 * SoldierDifficultyLevel(pSoldier))))
{
continue;
}
#ifdef DEBUGATTACKS
DebugAI(String("%s sees %s at gridno %d\n", pSoldier->GetName(), ExtMen[pOpponent->ubID].GetName(), pOpponent->sGridNo));
#endif
sMinAPcost = MinAPsToAttack(pSoldier, sTarget, DONTADDTURNCOST, 0);
// later will be decide if shoot is possible this here is just best guess so ignore turnover
// if we don't have enough APs left to shoot even a snap-shot at this guy
if (sMinAPcost > pSoldier->bActionPoints)
continue; // next opponent
// sevenfm: check CTGT and friendly fire for each stance instead since they can be different
// calculate chance to get through the opponent's cover (if any)
//dnl ch61 180813
/*gUnderFire.Clear();
gUnderFire.Enable();
ubChanceToGetThrough = AISoldierToSoldierChanceToGetThrough( pSoldier, pOpponent );
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// if we can't possibly get through all the cover
if (ubChanceToGetThrough == 0)
continue; // next opponent
// sevenfm: ignore opponent if we can hit friend
if (ubFriendlyFireChance > MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
continue;*/
if ((pSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) && (pSoldier->ubBodyType != QUEENMONSTER))
{
STRUCTURE_FILE_REF* pStructureFileRef;
UINT16 usAnimSurface;
usAnimSurface = DetermineSoldierAnimationSurface(pSoldier, pSoldier->usUIMovementMode);
pStructureFileRef = GetAnimationStructureRef(pSoldier->ubID, usAnimSurface, pSoldier->usUIMovementMode);
if (pStructureFileRef)
{
UINT16 usStructureID;
INT8 bDir;
// must make sure that structure data can be added in the direction of the target
bDir = (INT8)GetDirectionToGridNoFromGridNo(pSoldier->sGridNo, sTarget);
// ATE: Only if we have a levelnode...
if (pSoldier->pLevelNode != NULL && pSoldier->pLevelNode->pStructureData != NULL)
{
usStructureID = pSoldier->pLevelNode->pStructureData->usStructureID;
}
else
{
usStructureID = INVALID_STRUCTURE_ID;
}
if (!OkayToAddStructureToWorld(pSoldier->sGridNo, pSoldier->pathing.bLevel, &(pStructureFileRef->pDBStructureRef[gOneCDirection[bDir]]), usStructureID))
{
// can't turn in that dir.... next opponent
continue;
}
}
}
iBestHitRate = 0; // reset best hit rate to minimum
//dnl ch69 130913 Hoping to optimize
// consider alternate holding mode and different scopes
// sevenfm: alt weapon holding scope mode is used only when ubAllowAlternativeWeaponHolding == 3
for (pSoldier->bScopeMode = (gGameExternalOptions.ubAllowAlternativeWeaponHolding == 3 ? USE_ALT_WEAPON_HOLD : USE_BEST_SCOPE);
pSoldier->bScopeMode <= (gGameExternalOptions.fScopeModes ? NUM_SCOPE_MODES - 1 : USE_BEST_SCOPE);
pSoldier->bScopeMode++)
{
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD)
{
//dnl ch71 180913 throwing knives cannot be used in fire from hip
if (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE)
continue;
// sevenfm: hip firing allowed only for human bodytypes
if (!IS_MERC_BODY_TYPE(pSoldier))
continue;
}
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || (pSoldier->bScopeMode >= USE_BEST_SCOPE && ObjList[pSoldier->bScopeMode] != NULL))
{
usTrueState = pSoldier->usAnimState; // because is used in CalculateRaiseGunCost, CalcAimingLevelsAvailableWithAP, CalculateTurningCost
iTrueLastTarget = pSoldier->sLastTarget; // because is used in MinAPsToShootOrStab
// --------- Standing ---------
ubStance = ANIM_STAND;
// sevenfm: take into account direction when checking stance
// sevenfm: shoot heavy guns in standing stance only when using hip fire
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance) &&
(pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || !Weapon[pSoldier->usAttackingWeapon].HeavyGun || !ItemIsTwoHanded(pSoldier->usAttackingWeapon) || !gGameExternalOptions.ubAllowAlternativeWeaponHolding))
{
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
{
// Going up so first is stance change then turnover, do animation change before APs calculation
pSoldier->usAnimState = STANDING;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
if (fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
{
if (usRaiseGunCost > usTurningCost)
usTurningCost = 0;
else
usRaiseGunCost = 0;
}
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if (pSoldier->bActionPoints - sMinAPcost >= 0)
{
// calc next attack's minimum shooting cost (excludes readying & turning & raise gun)
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
{
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, STANDING);
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough ||
(Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE) && sChanceToHit > sBestChanceToHit)// rather take best chance for throwing knives
{
iBestHitRate = iHitRate;
sBestAimTime = sAimTime;
sBestChanceToHit = sChanceToHit;
ubBestChanceToGetThrough = ubChanceToGetThrough;
ubBestFriendlyFireChance = ubFriendlyFireChance;
bScopeMode = pSoldier->bScopeMode;
sBestAPcost = sMinAPcost;
ubBestStance = ubStance;
}
}
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->sLastTarget = iTrueLastTarget;
}
// no crouched/prone if we are tank/using throwing knife/hip firing
if (pSoldier->bScopeMode == USE_ALT_WEAPON_HOLD || ARMED_VEHICLE(pSoldier) || ENEMYROBOT(pSoldier) || (Item[pSoldier->usAttackingWeapon].usItemClass & IC_THROWING_KNIFE))
continue;
// --------- Crouched ---------
ubStance = ANIM_CROUCH;
// sevenfm: take into account direction
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
{
// change stance then turn
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
{
pSoldier->usAnimState = CROUCHING;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
if (fAddingTurningCost && fAddingRaiseGunCost)//dnl ch71 180913
{
if (usRaiseGunCost > usTurningCost)
usTurningCost = 0;
else
usRaiseGunCost = 0;
}
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if (pSoldier->bActionPoints - sMinAPcost >= 0)
{
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
{
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, CROUCHING);
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
{
iBestHitRate = iHitRate;
sBestAimTime = sAimTime;
sBestChanceToHit = sChanceToHit;
ubBestChanceToGetThrough = ubChanceToGetThrough;
ubBestFriendlyFireChance = ubFriendlyFireChance;
bScopeMode = pSoldier->bScopeMode;
sBestAPcost = sMinAPcost;
ubBestStance = ubStance;
}
}
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->sLastTarget = iTrueLastTarget;
}
// no prone stance if we have to change direction and stance at the same time
if (pSoldier->ubDirection != AIDirection(pSoldier->sGridNo, sTarget) &&
gAnimControl[pSoldier->usAnimState].ubEndHeight > ANIM_PRONE)
{
continue;
}
// --------- Prone ---------
ubStance = ANIM_PRONE;
if (pSoldier->InternalIsValidStance(AIDirection(pSoldier->sGridNo, sTarget), ubStance))
{
sStanceAPcost = GetAPsToChangeStance(pSoldier, ubStance);
if (sStanceAPcost)
{
pSoldier->usAnimState = PRONE;
pSoldier->sLastTarget = NOWHERE;
}
GetAPChargeForShootOrStabWRTGunRaises(pSoldier, sTarget, TRUE, &fAddingTurningCost, &fAddingRaiseGunCost, 0);
usTurningCost = CalculateTurningCost(pSoldier, pSoldier->usAttackingWeapon, fAddingTurningCost);
usRaiseGunCost = CalculateRaiseGunCost(pSoldier, fAddingRaiseGunCost, sTarget, 0);
sRawAPCost = MinAPsToShootOrStab(pSoldier, sTarget, 0, FALSE, 2);
sMinAPcost = sRawAPCost + usTurningCost + sStanceAPcost + usRaiseGunCost;
if (pSoldier->bActionPoints - sMinAPcost >= 0)
{
sMaxPossibleAimTime = CalcAimingLevelsAvailableWithAP(pSoldier, sTarget, pSoldier->bActionPoints - sMinAPcost);
// sevenfm: check CTGT and friendly fire chance for every stance
gUnderFire.Clear();
gUnderFire.Enable();
if (fSuppression)
ubChanceToGetThrough = SoldierToLocationChanceToGetThrough(pSoldier, sTarget, bLevel, 3, NOBODY);
else
ubChanceToGetThrough = SoldierToSoldierChanceToGetThrough(pSoldier, pOpponent);
ubFriendlyFireChance = gUnderFire.Chance(pSoldier->bTeam, pSoldier->bSide, TRUE);
gUnderFire.Disable();
// sevenfm: only use this stance if we can hit target and cannot hit friends
if (ubChanceToGetThrough > 0 && ubFriendlyFireChance <= MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE)
{
for (sAimTime = 0; sAimTime <= sMaxPossibleAimTime; sAimTime++)
{
sChanceToHit = AICalcChanceToHitGun(pSoldier, sTarget, sAimTime, AIM_SHOT_TORSO, bLevel, PRONE);
sAimAPCost = CalcAPCostForAiming(pSoldier, sTarget, (INT8)sAimTime);
iHitRate = sChanceToHit * (pSoldier->bActionPoints - (sMinAPcost - sRawAPCost)) / (sRawAPCost + sAimAPCost);
// sevenfm: take into account CTGT for every stance
if (iHitRate * ubChanceToGetThrough > iBestHitRate * ubBestChanceToGetThrough)
{
iBestHitRate = iHitRate;
sBestAimTime = sAimTime;
sBestChanceToHit = sChanceToHit;
ubBestChanceToGetThrough = ubChanceToGetThrough;
ubBestFriendlyFireChance = ubFriendlyFireChance;
bScopeMode = pSoldier->bScopeMode;
sBestAPcost = sMinAPcost;
ubBestStance = ubStance;
}
}
}
}
pSoldier->usAnimState = usTrueState;
pSoldier->sLastTarget = iTrueLastTarget;
}
}
}
// if we can't get any kind of hit rate at all
if (iBestHitRate == 0)
continue; // next opponent
// calculate chance to REALLY hit: shoot accurately AND get past cover
ubChanceToReallyHit = (UINT8)ceil((sBestChanceToHit * ubBestChanceToGetThrough) / 100.0f);
// if we can't REALLY hit at all
if (ubChanceToReallyHit == 0)
continue; // next opponent
// really limit knife throwing so it doesn't look wrong
if (Item[pSoldier->usAttackingWeapon].usItemClass == IC_THROWING_KNIFE &&
(ubChanceToReallyHit < 25 || (PythSpacesAway(pSoldier->sGridNo, sTarget) > CalcMaxTossRange(pSoldier, pSoldier->usAttackingWeapon, FALSE))))// Madd / 2 ) ) ) //dnl ch69 160913 was ubChanceToReallyHit < 30
continue; // don't bother... next opponent
// calculate this opponent's threat value (factor in my cover from him)
iThreatValue = CalcManThreatValue(pOpponent, pSoldier->sGridNo, TRUE, pSoldier);
// estimate the damage this shot would do to this opponent
iEstDamage = EstimateShotDamage(pSoldier, pOpponent, sBestChanceToHit);
//NumMessage("SHOT EstDamage = ",iEstDamage);
// calculate the combined "attack value" for this opponent
// highest possible value before division should be about 1.8 billion...
// normal value before division should be about 5 million...
iAttackValue = (iEstDamage * iBestHitRate * ubChanceToReallyHit * iThreatValue) / 1000;
//NumMessage("SHOT AttackValue = ",iAttackValue / 1000);
// sevenfm: penalize suppression fire
if (fSuppression)
{
// 25% penalty for shooting at invisible target
iAttackValue = iAttackValue / 2;
}
// special stuff for assassins to ignore militia more
if (pSoldier->IsAssassin() && pOpponent->bTeam == MILITIA_TEAM)
{
iAttackValue /= 2;
}
// sevenfm: empty vehicles have very low priority
if (pOpponent->ubWhatKindOfMercAmI == MERC_TYPE__VEHICLE && GetNumberInVehicle(pOpponent->bVehicleID) == 0)
{
iAttackValue /= 4;
}
// sevenfm: dying, cowering or unconscious soldiers have very low priority
if (pOpponent->stats.bLife < OKLIFE || pOpponent->bCollapsed || pOpponent->bBreathCollapsed)
{
iAttackValue /= 4;
}
#ifdef DEBUGATTACKS
DebugAI(String("CalcBestShot: best AttackValue vs %d = %d\n", uiLoop, iAttackValue));
#endif
// if we can hurt the guy, OR probably not, but at least it's our best
// chance to actually hit him and maybe scare him, knock him down, etc.
if ((iAttackValue > 0) || (ubChanceToReallyHit > pBestShot.ubChanceToReallyHit))
{
#if 0 // if there already was another viable target
if (pBestShot.ubChanceToReallyHit > 0)
{
// OK, how does our chance to hit him compare to the previous best one?
iPercentBetter = ((ubChanceToReallyHit * 100) / pBestShot.ubChanceToReallyHit) - 100;
//dnl ch62 180813 ignore firing into breathless targets if there are targets in better condition
// sevenfm: check that best opponent exists
if (pBestShot.ubOpponent != NOBODY &&
(Menptr[pBestShot.ubOpponent].bCollapsed || Menptr[pBestShot.ubOpponent].bBreathCollapsed) &&
Menptr[pBestShot.ubOpponent].bBreath < OKBREATH
&& Menptr[pBestShot.ubOpponent].bBreath < pOpponent->bBreath)
{
iPercentBetter = PERCENT_TO_IGNORE_THREAT;
}
// sevenfm: if best opponent is dying and new opponent is ok, use new opponent
if (pBestShot.ubOpponent != NOBODY &&
Menptr[pBestShot.ubOpponent].stats.bLife < OKLIFE &&
pOpponent->stats.bLife >= OKLIFE)
{
iPercentBetter = PERCENT_TO_IGNORE_THREAT;
}
// if this chance to really hit is more than 50% worse, and the other
// guy is conscious at all
if (iPercentBetter < -PERCENT_TO_IGNORE_THREAT &&
pBestShot.ubOpponent != NOBODY &&
Menptr[pBestShot.ubOpponent].stats.bLife >= OKLIFE)
{
// then stick with the older guy as the better target
continue;
}
// if this chance to really hit between 50% worse to 50% better
if (iPercentBetter < PERCENT_TO_IGNORE_THREAT)
{
// then the one with the higher ATTACK VALUE is the better target
if (iAttackValue < pBestShot.iAttackValue)
// the previous guy is more important since he's more dangerous
continue; // next opponent
}
}
// sevenfm: if new opponent is dying and best opponent is ok, ignore new opponent
if (pBestShot.ubOpponent != NOBODY &&
Menptr[pBestShot.ubOpponent].stats.bLife >= OKLIFE &&
pOpponent->stats.bLife < OKLIFE)
{
//DebugShot(pSoldier, String("new opponent is dying, best opponent is ok - skip"));
continue;
}
#endif
// OOOF! That was a lot of work! But we've got a new viable target!
pBestShot.ubPossible = TRUE;
pBestShot.ubOpponent = pOpponent->ubID;
pBestShot.ubAimTime = sBestAimTime;
pBestShot.ubChanceToReallyHit = ubChanceToReallyHit;
pBestShot.sTarget = sTarget;
pBestShot.bTargetLevel = bLevel;
pBestShot.iAttackValue = iAttackValue;
pBestShot.ubAPCost = sBestAPcost;
pBestShot.ubStance = ubBestStance;
pBestShot.bScopeMode = bScopeMode;
pBestShot.ubFriendlyFireChance = ubBestFriendlyFireChance;
possibleShots.push_back(pBestShot);
}
}
pSoldier->bScopeMode = USE_BEST_SCOPE; // better reset this back
}
bool CompareAttacks(const ATTACKTYPE& a, const ATTACKTYPE& b)
{
if (a.iAttackValue > b.iAttackValue)
return true;
else if (a.iAttackValue < b.iAttackValue)
return false;
if (a.ubChanceToReallyHit > b.ubChanceToReallyHit)
return true;
else if (a.ubChanceToReallyHit < b.ubChanceToReallyHit)
return false;
if (a.ubFriendlyFireChance < b.ubFriendlyFireChance)
return true;
else if (a.ubFriendlyFireChance > b.ubFriendlyFireChance)
return false;
if (a.ubAPCost < b.ubAPCost)
return true;
else if (a.ubAPCost > b.ubAPCost)
return false;
return false;
}
void CheckIfShotsPossible(SOLDIERTYPE* pSoldier, std::vector<ATTACKTYPE>& possibleShots)
{
INT8 guns[] = { FindAIUsableObjClass(pSoldier, IC_GUN), FindAIUsableObjClass(pSoldier, IC_GUN, TRUE) };
// Only merc bodytypes can use a sidearm, also don't bother to check for targets if found sidearm is the same as main gun
if (!IS_MERC_BODY_TYPE(pSoldier) || guns[0] == guns[1])
{
guns[1] = NO_SLOT;
}
for (size_t i = 0; i < 2; i++)
{
const auto gun = guns[i];
// if the soldier does have a gun
if (gun != NO_SLOT)
{
// if it's in his holster, swap it into his hand temporarily
if (gun != HANDPOS)
{
RearrangePocket(pSoldier, HANDPOS, gun, TEMPORARILY);
}
// get the minimum cost to attack with this item
INT16 ubMinAPcost = MinAPsToAttack(pSoldier, pSoldier->sLastTarget, ADDTURNCOST, 0);
// if we can afford the minimum AP cost
if (pSoldier->bActionPoints >= ubMinAPcost)
{
// then look around for worthy targets
CalculatePossibleShots(pSoldier, gun, possibleShots);
}
// if it was in his holster, swap it back into his holster for now
if (gun != HANDPOS)
{
RearrangePocket(pSoldier, HANDPOS, gun, TEMPORARILY);
}
}
}
}
+2
View File
@@ -14,4 +14,6 @@ set(TacticalAISrc
"${CMAKE_CURRENT_SOURCE_DIR}/QuestDebug.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Realtime.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/ZombieDecideAction.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/UtilityAI.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/UtilityAI_ResponseCurve.cpp"
PARENT_SCOPE)
+7 -5
View File
@@ -844,7 +844,8 @@ INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
{
// 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);
INT32 distanceToThreat;
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
if (!TileIsOutOfBounds(sClosestOpponent))
{
@@ -854,9 +855,9 @@ INT8 CreatureDecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
// 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 );
sDistVisible = pSoldier->GetMaxDistanceVisible(sClosestOpponent, 0 ) * CELL_X_SIZE;
if ((pSoldier->ubDirection != ubOpponentDir) && (PythSpacesAway(pSoldier->sGridNo,sClosestOpponent) <= sDistVisible))
if ((pSoldier->ubDirection != ubOpponentDir) && (distanceToThreat <= sDistVisible))
{
// set base chance according to orders
if ((pSoldier->aiData.bOrders == STATIONARY) || (pSoldier->aiData.bOrders == ONGUARD))
@@ -1380,11 +1381,12 @@ INT8 CreatureDecideActionBlack( SOLDIERTYPE * 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 = ClosestKnownOpponent(pSoldier, NULL, NULL);
INT32 distanceToThreat;
sClosestOpponent = ClosestKnownOpponent(pSoldier, NULL, NULL, NULL, &distanceToThreat);
// if we have a closest reachable opponent
if (!TileIsOutOfBounds(sClosestOpponent))
{
if ( ubCanMove && PythSpacesAway( pSoldier->sGridNo, sClosestOpponent ) > 2 )
if ( ubCanMove && distanceToThreat > 20 ) // 2 tiles -> 20 in fractional Cell Coordinates
{
if ( bSpitIn != NO_SLOT )
{
+12611 -288
View File
File diff suppressed because it is too large Load Diff
+138 -287
View File
@@ -15,10 +15,6 @@
#include "Render Fun.h"
#include "Boxing.h"
#include "Text.h"
#ifdef _DEBUG
#include "renderworld.h"
#include "video.h"
#endif
#include "worldman.h"
#include "strategicmap.h"
#include "environment.h"
@@ -26,7 +22,8 @@
#include "Buildings.h"
#include "GameSettings.h"
#include "Soldier Profile.h"
#include "Rotting Corpses.h" // sevenfm
#include "rotting corpses.h" // sevenfm
#include <Cheats.h>
////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,17 +31,7 @@
// were changed to GetAPsCrouch() and GetAPsProne()
// - also all "APBPConstants[AP_PICKUP_ITEM]" were replaced by GetBasicAPsToPickupItem()
////////////////////////////////////////////////////////////////////////////////////////////////////////
INT16 * gsCoverValue = NULL;
#ifdef _DEBUG
INT16 gsBestCover;
#ifndef PATHAI_VISIBLE_DEBUG
// NB Change this to true to get visible cover debug -- CJC
BOOLEAN gfDisplayCoverValues = FALSE;
#endif
extern void RenderCoverDebug( void );
#endif
INT16 gsBestCover;
INT16 gubAIPathCosts[19][19];
@@ -613,42 +600,115 @@ UINT8 NumberOfTeamMatesAdjacent( SOLDIERTYPE * pSoldier, INT32 sGridNo )
return( ubCount );
}
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter)
static void CalculateCoverValue(SOLDIERTYPE* pSoldier, const INT32 sGridNo, const INT32 iPathCost, const INT32 iMyThreatValue, const UINT32 uiThreatCnt, const UINT8 ubDiff, const BOOLEAN fNight, const UINT8 ubBackgroundLightPercent, const INT32 morale, INT32 &iCoverValue, INT32& iCoverScale)
{
iCoverValue = 0;
iCoverScale = 0;
// sevenfm: check sight cover
BOOLEAN fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (UINT32 uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
{
// calculate the range we would be at from this opponent
INT32 iThreatRange = Threat[uiLoop].iOrigRange;
if (sGridNo != pSoldier->sGridNo)
{
iThreatRange = GetRangeInCellCoordsFromGridNoDiff(sGridNo, Threat[uiLoop].sGridNo);
}
// if this threat would be within 20 tiles, count it
if (iThreatRange <= MAX_THREAT_RANGE)
{
iCoverValue += CalcCoverValue(
pSoldier, sGridNo, iMyThreatValue,
(pSoldier->bActionPoints - iPathCost),
uiLoop, iThreatRange, morale, &iCoverScale
);
}
// sevenfm: sight test
if (gGameExternalOptions.fAIBetterCover)
{
if (LocationToLocationLineOfSightTest(Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS))
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCoverValue after opponent %d is now %d",iLoop,iCoverValue);
//PopMessage(tempstr);
}
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
iCoverValue -= (abs(iCoverValue) / 5) * NumberOfTeamMatesAdjacent(pSoldier, sGridNo);
if (gGameExternalOptions.fAIBetterCover)
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if (RangeChangeDesire(pSoldier) < 4)
{
if (fProneCover) { iCoverValue += abs(iCoverValue) / __max(2, 2 * RangeChangeDesire(pSoldier)); }
}
// sevenfm: check for nearby friends, add bonus/penalty 10%
UINT8 ubNearbyFriends = __max(0, CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_CLOSE));
iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (6 - ubDiff);
// sevenfm: penalize locations with fresh corpses
if (GetNearestRottingCorpseAIWarning(sGridNo) > 0)
{
iCoverValue -= abs(iCoverValue) / (6 - ubDiff);
}
// sevenfm: penalize locations near red smoke
iCoverValue -= abs(iCoverValue) * RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) / 100;
}
if (fNight && !(InARoom(sGridNo, NULL))) // ignore in buildings in case placed there
{
// reduce cover at nighttime based on how bright the light is at that location
// using the difference in sighting distance between the background and the
// light for this tile
//ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
UINT8 ubLightPercentDifference = (gGameExternalOptions.ubBrightnessVisionMod[LightTrueLevel(sGridNo, pSoldier->pathing.bLevel)] - ubBackgroundLightPercent);
iCoverValue -= (abs(iCoverValue) / 100) * ubLightPercentDifference;
}
}
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentBetter, INT32 targetGridNo, bool ignoreSearchRange)
{
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
{
ResetDebugInfoValues();
}
DebugMsg(TOPIC_JA2AI,DBG_LEVEL_3,String("FindBestNearbyCover"));
// all 32-bit integers for max. speed
UINT32 uiLoop;
INT32 iCurrentCoverValue, iCoverValue, iBestCoverValue;
INT32 iCurrentScale = -1, iCoverScale = -1, iBestCoverScale = -1;
INT32 iDistFromOrigin, iDistCoverFromOrigin;
//INT32 iThreatCertainty;
INT32 sGridNo, sBestCover = NOWHERE;
INT32 iPathCost;
INT32 iThreatRange, iClosestThreatRange = 1500;
// INT16 sClosestThreatGridno = NOWHERE;
INT32 iMyThreatValue;
//INT32 sThreatLoc;
//INT32 iMaxThreatRange;
UINT32 uiThreatCnt = 0;
INT32 iMaxMoveTilesLeft, iSearchRange, iRoamRange;
INT16 sMaxLeft, sMaxRight, sMaxUp, sMaxDown, sXOffset, sYOffset;
INT32 sOrigin; // has to be a short, need a pointer
INT32 * pusLastLoc;
INT8 * pbPersOL;
INT8 * pbPublOL;
//SOLDIERTYPE *pOpponent;
UINT16 usMovementMode;
UINT8 ubBackgroundLightLevel;
UINT8 ubBackgroundLightPercent = 0;
UINT8 ubLightPercentDifference;
BOOLEAN fNight;
// sevenfm
UINT8 ubNearbyFriends;
BOOLEAN fProneCover;
UINT8 ubDiff = SoldierDifficultyLevel( pSoldier );
const UINT8 ubDiff = SoldierDifficultyLevel( pSoldier );
if (targetGridNo == NOWHERE)
{
targetGridNo = pSoldier->sGridNo;
}
// There's no cover when boxing!
if (gTacticalStatus.bBoxingState == BOXING)
@@ -676,25 +736,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
}
}
iBestCoverValue = -1;
#if defined( _DEBUG ) && defined( COVER_DEBUG )
if (gfDisplayCoverValues)
{
memset( gsCoverValue, 0x7F, sizeof( INT16 ) * WORLD_MAX );
}
#endif
//NameMessage(pSoldier,"looking for some cover...");
// BUILD A LIST OF THREATENING GRID #s FROM PERSONAL & PUBLIC opplists
pusLastLoc = &(gsLastKnownOppLoc[pSoldier->ubID][0]);
// hang a pointer into personal opplist
pbPersOL = &(pSoldier->aiData.bOppList[0]);
// hang a pointer into public opplist
pbPublOL = &(gbPublicOpplist[pSoldier->bTeam][0]);
// decide how far we're gonna be looking
iSearchRange = gbDiff[DIFF_MAX_COVER_RANGE][ SoldierDifficultyLevel( pSoldier ) ];
@@ -711,9 +752,9 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
}*/
// maximum search range is 1 tile / 8 pts of wisdom
if (iSearchRange > (pSoldier->stats.bWisdom / 8))
if (iSearchRange > (pSoldier->stats.bWisdom / 4))
{
iSearchRange = (pSoldier->stats.bWisdom / 8);
iSearchRange = (pSoldier->stats.bWisdom / 4);
}
if (!gfTurnBasedAI)
@@ -724,6 +765,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
usMovementMode = DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER );
#if 1
if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle
{
// must be able to reach the cover, so it can't possibly be more than
@@ -744,6 +786,12 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
iSearchRange = iMaxMoveTilesLeft;
}
}
#endif
if (ignoreSearchRange)
{
// For debugging
iSearchRange = 16;
}
if (iSearchRange <= 0)
{
@@ -765,95 +813,30 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// calculate our current cover value in the place we are now, since the
// cover we are searching for must be better than what we have now!
iCurrentCoverValue = 0;
iCurrentScale = 0;
CalculateCoverValue(pSoldier, targetGridNo, 0, iMyThreatValue, uiThreatCnt, ubDiff, fNight, ubBackgroundLightPercent, morale, iCurrentCoverValue, iCurrentScale);
// the initial cover value to beat is our current cover value
iBestCoverValue = iCurrentCoverValue;
// sevenfm: sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
{
// if this threat is CURRENTLY within 20 tiles
if (Threat[uiLoop].iOrigRange <= MAX_THREAT_RANGE)
{
// add this opponent's cover value to our current total cover value
iCurrentCoverValue += CalcCoverValue(pSoldier,pSoldier->sGridNo,iMyThreatValue,pSoldier->bActionPoints,uiLoop,Threat[uiLoop].iOrigRange,morale,&iCurrentScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, pSoldier->sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) )
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, pSoldier->sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, NO_DISTANCE_LIMIT ) )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCurrentCoverValue after opponent %d is now %d",iLoop,iCurrentCoverValue);
//PopMessage(tempstr);
gRenderDebugInfoValues[targetGridNo] = (INT32)(iCurrentCoverValue / 100);
}
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
if ( iCurrentCoverValue >= 0 )
{
iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
else
{
// when negative, must add a negative to decrease the total
iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
{
iCurrentCoverValue += abs(iCurrentCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
}
// sevenfm: check for nearby friends, add bonus/penalty
ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, pSoldier->sGridNo, 5 ));
iCurrentCoverValue -= ubNearbyFriends * abs(iCurrentCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( pSoldier->sGridNo ) > 0)
{
iCurrentCoverValue -= abs(iCurrentCoverValue) / (8-ubDiff);
}
// sevenfm: penalize locations near red smoke
iCurrentCoverValue -= abs(iCurrentCoverValue) * RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel) / 100;
}
#ifdef DEBUGCOVER
// AINumMessage("Search Range = ",iSearchRange);
#endif
// determine maximum horizontal limits
sMaxLeft = min(iSearchRange,(pSoldier->sGridNo % MAXCOL));
//NumMessage("sMaxLeft = ",sMaxLeft);
sMaxRight = min(iSearchRange,MAXCOL - ((pSoldier->sGridNo % MAXCOL) + 1));
//NumMessage("sMaxRight = ",sMaxRight);
sMaxLeft = min(iSearchRange,(targetGridNo % MAXCOL));
sMaxRight = min(iSearchRange,MAXCOL - ((targetGridNo % MAXCOL) + 1));
// determine maximum vertical limits
sMaxUp = min(iSearchRange,(pSoldier->sGridNo / MAXROW));
//NumMessage("sMaxUp = ",sMaxUp);
sMaxDown = min(iSearchRange,MAXROW - ((pSoldier->sGridNo / MAXROW) + 1));
//NumMessage("sMaxDown = ",sMaxDown);
sMaxUp = min(iSearchRange,(targetGridNo / MAXROW));
sMaxDown = min(iSearchRange,MAXROW - ((targetGridNo / MAXROW) + 1));
iRoamRange = RoamingRange(pSoldier,&sOrigin);
// if status isn't black (life & death combat), and roaming range is limited
if ((pSoldier->aiData.bAlertStatus != STATUS_BLACK) && (iRoamRange < MAX_ROAMING_RANGE) &&
//if ((pSoldier->aiData.bAlertStatus != STATUS_BLACK) && (iRoamRange < MAX_ROAMING_RANGE) &&
if ((pSoldier->aiData.bAlertStatus < STATUS_RED) && (iRoamRange < MAX_ROAMING_RANGE) &&
(!TileIsOutOfBounds(sOrigin)))
{
// must try to stay within or return to the point of origin
iDistFromOrigin = SpacesAway(sOrigin,pSoldier->sGridNo);
iDistFromOrigin = SpacesAway(sOrigin,targetGridNo);
}
else
{
@@ -862,19 +845,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
}
#ifdef DEBUGCOVER
DebugAI( String( "FBNC: iRoamRange %d, sMaxLeft %d, sMaxRight %d, sMaxUp %d, sMaxDown %d\n",iRoamRange,sMaxLeft,sMaxRight,sMaxUp,sMaxDown) );
#endif
// the initial cover value to beat is our current cover value
iBestCoverValue = iCurrentCoverValue;
#ifdef DEBUGDECISIONS
STR tempstr="";
sprintf( tempstr, "FBNC: CURRENT iCoverValue = %d\n",iCurrentCoverValue );
DebugAI( tempstr );
#endif
if (pSoldier->aiData.bAlertStatus >= STATUS_RED) // if already in battle
{
// to speed this up, tell PathAI to cancel any paths beyond our AP reach!
@@ -900,7 +870,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{
continue;
@@ -908,22 +878,20 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
gpWorldLevelData[sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
}
}
FindBestPath( pSoldier, GRIDSIZE, pSoldier->pathing.bLevel, DetermineMovementMode( pSoldier, AI_ACTION_TAKE_COVER ), COPYREACHABLE_AND_APS, 0 );//dnl ch50 071009
// Turn off the "reachable" flag for his current location
// so we don't consider it
gpWorldLevelData[pSoldier->sGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
gpWorldLevelData[targetGridNo].uiFlags &= ~(MAPELEMENT_REACHABLE);
// SET UP DOUBLE-LOOP TO STEP THROUGH POTENTIAL GRID #s
for (sYOffset = -sMaxUp; sYOffset <= sMaxDown; sYOffset++)
{
for (sXOffset = -sMaxLeft; sXOffset <= sMaxRight; sXOffset++)
{
//HandleMyMouseCursor(KEYBOARDALSO);
// calculate the next potential gridno
sGridNo = pSoldier->sGridNo + sXOffset + (MAXCOL * sYOffset);
sGridNo = targetGridNo + sXOffset + (MAXCOL * sYOffset);
if ( !(sGridNo >=0 && sGridNo < WORLD_MAX) )
{
continue;
@@ -980,7 +948,7 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// sevenfm: avoid moving into light
if (InLightAtNight(sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(pSoldier->sGridNo, pSoldier->pathing.bLevel) &&
!InLightAtNight(targetGridNo, pSoldier->pathing.bLevel) &&
!pSoldier->aiData.bUnderFire)
{
continue;
@@ -989,12 +957,19 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// avoid moving into red smoke
if (gGameExternalOptions.fAIBetterCover &&
RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) &&
!RedSmokeDanger(pSoldier->sGridNo, pSoldier->pathing.bLevel))
!RedSmokeDanger(targetGridNo, pSoldier->pathing.bLevel))
{
//DebugCover(pSoldier, String("moving into red smoke, skip"));
continue;
}
// Avoid overcrowding
const auto nearbyFriends = CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_VERYCLOSE);
if ( nearbyFriends > 1 && !pSoldier->IsZombie() )
{
continue;
}
// zombies only want to hide
if (pSoldier->IsZombie() && !SightCoverAtSpot(pSoldier, sGridNo, TRUE))
{
@@ -1027,110 +1002,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// OK, this place shows potential. How useful is it as cover?
// EVALUATE EACH GRID #, remembering the BEST PROTECTED ONE
iCoverValue = 0;
iCoverScale = 0;
CalculateCoverValue(pSoldier, sGridNo, iPathCost, iMyThreatValue, uiThreatCnt, ubDiff, fNight, ubBackgroundLightPercent, morale, iCoverValue, iCoverScale);
// sevenfm: check sight cover
fProneCover = TRUE;
// for every opponent that threatens, consider this spot's cover vs. him
for (uiLoop = 0; uiLoop < uiThreatCnt; uiLoop++)
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
{
// calculate the range we would be at from this opponent
iThreatRange = GetRangeInCellCoordsFromGridNoDiff( sGridNo, Threat[uiLoop].sGridNo );
// if this threat would be within 20 tiles, count it
if (iThreatRange <= MAX_THREAT_RANGE)
{
iCoverValue += CalcCoverValue(pSoldier,sGridNo,iMyThreatValue,
(pSoldier->bActionPoints - iPathCost),
uiLoop,iThreatRange,morale,&iCoverScale);
}
// sevenfm: sight test
if( gGameExternalOptions.fAIBetterCover )
{
if ( LocationToLocationLineOfSightTest( Threat[uiLoop].sGridNo, Threat[uiLoop].pOpponent->pathing.bLevel, sGridNo, pSoldier->pathing.bLevel, TRUE, MAX_VISION_RANGE, STANDING_LOS_POS, PRONE_LOS_POS ) )
//if ( SoldierToVirtualSoldierLineOfSightTest( Threat[uiLoop].pOpponent, sGridNo, pSoldier->pathing.bLevel, ANIM_PRONE, TRUE, -1 ) != 0 )
{
fProneCover = FALSE;
}
}
//sprintf(tempstr,"iCoverValue after opponent %d is now %d",iLoop,iCoverValue);
//PopMessage(tempstr);
gRenderDebugInfoValues[sGridNo] = (INT32) (iCoverValue / 100);
}
// reduce cover for each person adjacent to this gridno who is on our team,
// by 10% (so locations next to several people will be very much frowned upon
if ( iCoverValue >= 0 )
{
iCoverValue -= (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
else
{
// when negative, must add a negative to decrease the total
iCoverValue += (iCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, sGridNo );
}
if( gGameExternalOptions.fAIBetterCover )
{
// sevenfm: when defending (range change <= 3), prefer locations with sight cover
if( RangeChangeDesire(pSoldier) < 4 )
{
if( fProneCover )
iCoverValue += abs(iCoverValue) / __max(2, 2*RangeChangeDesire(pSoldier));
}
// sevenfm: check for nearby friends in 10 radius, add bonus/penalty 10%
ubNearbyFriends = __min(5, CountNearbyFriends( pSoldier, sGridNo, 5 ));
iCoverValue -= ubNearbyFriends * abs(iCoverValue) / (10-ubDiff);
// sevenfm: penalize locations with fresh corpses
if(GetNearestRottingCorpseAIWarning( sGridNo ) > 0)
{
iCoverValue -= abs(iCoverValue) / (8-ubDiff);
}
// sevenfm: penalize locations near red smoke
iCoverValue -= abs(iCoverValue) * RedSmokeDanger(sGridNo, pSoldier->pathing.bLevel) / 100;
}
if ( fNight && !( InARoom( sGridNo, NULL ) ) ) // ignore in buildings in case placed there
{
// reduce cover at nighttime based on how bright the light is at that location
// using the difference in sighting distance between the background and the
// light for this tile
//ubLightPercentDifference = (gbLightSighting[ 0 ][ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
ubLightPercentDifference = (gGameExternalOptions.ubBrightnessVisionMod[ LightTrueLevel( sGridNo, pSoldier->pathing.bLevel ) ] - ubBackgroundLightPercent );
if ( iCoverValue >= 0 )
{
iCoverValue -= (iCoverValue / 100) * ubLightPercentDifference;
}
else
{
iCoverValue += (iCoverValue / 100) * ubLightPercentDifference;
}
}
#ifdef DEBUGCOVER
// if there ARE multiple opponents
if (uiThreatCnt > 1)
{
DebugAI( String( "FBNC: Total iCoverValue at gridno %d is %d\n\n",sGridNo,iCoverValue ) );
}
#endif
#if defined( _DEBUG ) && defined( COVER_DEBUG )
if (gfDisplayCoverValues)
{
gsCoverValue[sGridNo] = (INT16) (iCoverValue / 100);
}
#endif
// if this is better than the best place found so far
if (iCoverValue > iBestCoverValue)
{
// ONLY DO THIS CHECK HERE IF WE'RE WAITING FOR OPPCHANCETODECIDE,
@@ -1148,11 +1028,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
}
*/
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr,"FBNC: NEW BEST iCoverValue at gridno %d is %d\n",sGridNo,iCoverValue );
DebugAI( tempstr );
#endif
// remember it instead
sBestCover = sGridNo;
iBestCoverValue = iCoverValue;
@@ -1164,34 +1039,15 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
gubNPCAPBudget = 0;
gubNPCDistLimit = 0;
#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
if (gfDisplayCoverValues)
if (gRenderDebugInfoMode == DEBUG_COVERVALUE && DEBUG_CHEAT_LEVEL())
{
// do a locate?
LocateSoldier( pSoldier->ubID, SETLOCATORFAST );
gsBestCover = sBestCover;
SetRenderFlags( RENDER_FLAG_FULL );
RenderWorld();
RenderCoverDebug( );
InvalidateScreen( );
EndFrameBufferRender();
RefreshScreen( NULL );
/*
iLoop = GetJA2Clock();
do
{
} while( ( GetJA2Clock( ) - iLoop ) < 2000 );
*/
InvalidateRegion(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_WINDOW_END_Y);
}
#endif
// if a better cover location was found
if (!TileIsOutOfBounds(sBestCover))
{
#if defined( _DEBUG ) && !defined( PATHAI_VISIBLE_DEBUG )
gsBestCover = sBestCover;
#endif
// cover values already take the AP cost of getting there into account in
// a BIG way, so no need to worry about that here, even small improvements
// are actually very significant once we get our APs back (if we live!)
@@ -1200,16 +1056,6 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
// if best cover value found was at least 5% better than our current cover
if (*piPercentBetter >= MIN_PERCENT_BETTER)
{
#ifdef DEBUGDECISIONS
STR tempstr;
sprintf( tempstr,"Found Cover: current %ld, best %ld, %%%%Better %ld\n", iCurrentCoverValue,iBestCoverValue,*piPercentBetter );
DebugAI( tempstr );
#endif
#ifdef BETAVERSION
SnuggleDebug(pSoldier,"Found Cover");
#endif
return(sBestCover); // return the gridno of that cover
}
}
@@ -3128,7 +2974,11 @@ INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UI
{
if (usMovementMode == RUNNING || usMovementMode == WALKING)
{
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE) + GetAPsProne(pSoldier, TRUE);
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE) +GetAPsProne(pSoldier, TRUE);
if (pSoldier->bActionPoints <= ubReserveAP)
{
ubReserveAP = APBPConstants[AP_CHANGE_FACING] + GetAPsCrouch(pSoldier, TRUE);
}
}
else
{
@@ -3279,7 +3129,8 @@ INT32 FindAdvanceSpot(SOLDIERTYPE *pSoldier, INT32 sTargetSpot, INT8 bAction, UI
}
// avoid overcrowding
if (!pSoldier->IsZombie() && NumberOfTeamMatesAdjacent(pSoldier, sGridNo) > 1)
const auto nearbyFriends = CountNearbyFriends(pSoldier, sGridNo, TACTICAL_RANGE_VERYCLOSE);
if ( nearbyFriends > 1 && !pSoldier->IsZombie() )
{
continue;
}
+107 -111
View File
@@ -432,7 +432,7 @@ INT8 RandomPointPatrolAI(SOLDIERTYPE *pSoldier)
INT32 InternalGoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, INT16 bReserveAPs, INT8 bAction, INT8 fFlags )
{
#ifdef DEBUGDECISIONS
STR16 tempstr;
STR16 tempstr;
#endif
INT32 sLoop,sAPCost;
INT32 sTempDest,sGoToGrid;
@@ -588,156 +588,152 @@ INT32 InternalGoAsFarAsPossibleTowards(SOLDIERTYPE *pSoldier, INT32 sDesGrid, IN
}
}
// HAVE FOUND AN OK destination AND PLOTTED A VALID BEST PATH TO IT
// HAVE FOUND AN OK destination AND PLOTTED A VALID BEST PATH TO IT
#ifdef DEBUGDECISIONS
AINumMessage("Chosen legal destination is gridno ",sDesGrid);
AINumMessage("Tracing along path, pathRouteToGo = ",pSoldier->pathing.usPathIndex);
AINumMessage("Chosen legal destination is gridno ",sDesGrid);
AINumMessage("Tracing along path, pathRouteToGo = ",pSoldier->pathing.usPathIndex);
#endif
sGoToGrid = pSoldier->sGridNo; // start back where soldier is standing now
sAPCost = 0; // initialize path cost counter
sGoToGrid = pSoldier->sGridNo; // start back where soldier is standing now
sAPCost = 0; // initialize path cost counter
// 0verhaul: If the destination is within the patrol route, allow it. This is necessary to allow an errant soldier
// to return to his patrol route if flanking or other actions have pulled him beyond his allowed range from origin.
if (SpacesAway(sOrigin,sDesGrid) <= usMaxDist)
{
fAllowDest = TRUE;
}
// 0verhaul: If the destination is within the patrol route, allow it. This is necessary to allow an errant soldier
// to return to his patrol route if flanking or other actions have pulled him beyond his allowed range from origin.
if (SpacesAway(sOrigin,sDesGrid) <= usMaxDist)
{
fAllowDest = TRUE;
}
// we'll only go as far along the plotted route as is within our
// permitted roaming range, and we'll stop as soon as we're down to <= 5 APs
// we'll only go as far along the plotted route as is within our
// permitted roaming range, and we'll stop as soon as we're down to <= 5 APs
sTempDest = pSoldier->sGridNo;
for (sLoop = 0; sLoop < (pSoldier->pathing.usPathDataSize - pSoldier->pathing.usPathIndex); sLoop++)
for (sLoop = 0; sLoop < (pSoldier->pathing.usPathDataSize - pSoldier->pathing.usPathIndex); sLoop++)
{
// what is the next gridno in the path?
// what is the next gridno in the path?
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (INT16) (pSoldier->pathing.usPathingData[sLoop] + 1) ) );
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
sTempDest = NewGridNo( sTempDest,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (INT16) (pSoldier->pathing.usPathingData[sLoop] + 1) ) );
//sTempDest = NewGridNo( sGoToGrid,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
sTempDest = NewGridNo( sTempDest,DirectionInc( (UINT8) (pSoldier->pathing.usPathingData[sLoop]) ) );
// this should NEVER be out of bounds
if (sTempDest == sGoToGrid)
{
// this should NEVER be out of bounds
if (sTempDest == sGoToGrid)
{
#ifdef BETAVERSION
sprintf(tempstr,"GoAsFarAsPossibleTowards: ERROR - gridno along valid route is invalid! guynum %d, sTempDest = %d",pSoldier->ubID,sTempDest);
sprintf(tempstr,"GoAsFarAsPossibleTowards: ERROR - gridno along valid route is invalid! guynum %d, sTempDest = %d",pSoldier->ubID,sTempDest);
#ifdef RECORDNET
fprintf(NetDebugFile,"\n\t%s\n",tempstr);
fprintf(NetDebugFile,"\n\t%s\n",tempstr);
#endif
PopMessage(tempstr);
SaveGame(ERROR_SAVE);
PopMessage(tempstr);
SaveGame(ERROR_SAVE);
#endif
break; // quit here, sGoToGrid is where we are going
}
// if this takes us beyond our permitted "roaming range"
// but if it brings us closer, then allow it!
if (SpacesAway(sOrigin,sTempDest) > usMaxDist && !fAllowDest)
break; // quit here, sGoToGrid is where we are going
if ( usRoomRequired )
{
if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) )
{
// quit here, limited by room!
break;
break; // quit here, sGoToGrid is where we are going
}
}
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
{
break; // quit here, sGoToGrid is where we are going
}
// if this takes us beyond our permitted "roaming range"
// but if it brings us closer, then allow it!
if (SpacesAway(sOrigin,sTempDest) > usMaxDist && !fAllowDest)
break; // quit here, sGoToGrid is where we are going
// CAN'T CALL PathCost() HERE! IT CALLS findBestPath() and overwrites
// pathRouteToGo !!! Gotta calculate the cost ourselves - Ian
//
//ubAPsLeft = pSoldier->bActionPoints - PathCost(pSoldier,sTempDest,FALSE,FALSE,FALSE,FALSE,FALSE);
if (gfTurnBasedAI)
{
// if we're just starting the "costing" process (first gridno)
if (sLoop == 0)
{
if (pSoldier->usUIMovementMode == RUNNING)
{
sAPCost += GetAPsStartRun( pSoldier ); // changed by SANDRO
}
}
// ATE: Direction here?
sAPCost += EstimateActionPointCost( pSoldier, sTempDest, (INT8) pSoldier->pathing.usPathingData[sLoop], pSoldier->usUIMovementMode, (INT8) sLoop, (INT8) pSoldier->pathing.usPathDataSize );
bAPsLeft = pSoldier->bActionPoints - sAPCost;
}
// if this gridno is NOT a legal NPC destination
// DONT'T test path again - that would replace the traced path! - Ian
// NOTE: It's OK to go *THROUGH* water to try and get to the destination!
// sevenfm: jump over fence code - if current gridno is not legal destination, check next gridno
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0))
{
// break;
continue;
}
// if after this, we have <= 5 APs remaining, that's far enough, break out
// (the idea is to preserve APs so we can crouch or react if
// necessary, and benefit from the carry-over next turn if not needed)
// This routine is NOT used by any GREEN AI, so such caution is warranted!
if ( gfTurnBasedAI && (bAPsLeft < bReserveAPs) )
break;
else
if ( usRoomRequired )
{
sGoToGrid = sTempDest; // we're OK up to here
if ( !( InARoom( sTempDest, &usTempRoom ) && usTempRoom == usRoomRequired ) )
{
// quit here, limited by room!
break;
}
}
// if exactly 5 APs left, don't bother checking any further
if ( gfTurnBasedAI && (bAPsLeft == bReserveAPs) )
if ( (fFlags & FLAG_STOPSHORT) && SpacesAway( sDesGrid, sTempDest ) <= STOPSHORTDIST )
{
break; // quit here, sGoToGrid is where we are going
}
// CAN'T CALL PathCost() HERE! IT CALLS findBestPath() and overwrites
// pathRouteToGo !!! Gotta calculate the cost ourselves - Ian
//
//ubAPsLeft = pSoldier->bActionPoints - PathCost(pSoldier,sTempDest,FALSE,FALSE,FALSE,FALSE,FALSE);
if (gfTurnBasedAI)
{
// if we're just starting the "costing" process (first gridno)
if (sLoop == 0)
{
if (pSoldier->usUIMovementMode == RUNNING)
{
sAPCost += GetAPsStartRun( pSoldier ); // changed by SANDRO
}
}
// ATE: Direction here?
sAPCost += EstimateActionPointCost( pSoldier, sTempDest, (INT8) pSoldier->pathing.usPathingData[sLoop], pSoldier->usUIMovementMode, (INT8) sLoop, (INT8) pSoldier->pathing.usPathDataSize );
bAPsLeft = pSoldier->bActionPoints - sAPCost;
}
// if this gridno is NOT a legal NPC destination
// DONT'T test path again - that would replace the traced path! - Ian
// NOTE: It's OK to go *THROUGH* water to try and get to the destination!
// sevenfm: jump over fence code - if current gridno is not legal destination, check next gridno
if (!LegalNPCDestination(pSoldier,sTempDest,IGNORE_PATH,WATEROK,0))
{
// break;
continue;
}
// if after this, we have <= 5 APs remaining, that's far enough, break out
// (the idea is to preserve APs so we can crouch or react if
// necessary, and benefit from the carry-over next turn if not needed)
// This routine is NOT used by any GREEN AI, so such caution is warranted!
if ( gfTurnBasedAI && (bAPsLeft < bReserveAPs) )
break;
else
{
sGoToGrid = sTempDest; // we're OK up to here
// if exactly 5 APs left, don't bother checking any further
if ( gfTurnBasedAI && (bAPsLeft == bReserveAPs) )
break;
}
}
// if it turned out we couldn't go even 1 tile towards the desired gridno
if (sGoToGrid == pSoldier->sGridNo)
// if it turned out we couldn't go even 1 tile towards the desired gridno
if (sGoToGrid == pSoldier->sGridNo)
{
#ifdef DEBUGDECISIONS
sprintf(tempstr,"%s will go NOWHERE, path doesn't meet criteria",pSoldier->name);
AIPopMessage(tempstr);
sprintf(tempstr,"%s will go NOWHERE, path doesn't meet criteria",pSoldier->name);
AIPopMessage(tempstr);
#endif
pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0;
return(NOWHERE); // then go nowhere
pSoldier->pathing.usPathIndex = pSoldier->pathing.usPathDataSize = 0;
return(NOWHERE); // then go nowhere
}
else
{
// possible optimization - stored path IS good if we're going all the way
if (sGoToGrid == sDesGrid)
else
{
pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
}
else if ( pSoldier->pathing.usPathIndex == 0 )
{
// we can hack this surely! -- CJC
pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
pSoldier->pathing.usPathDataSize = sLoop + 1;
}
// possible optimization - stored path IS good if we're going all the way
if (sGoToGrid == sDesGrid)
{
pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
}
else if ( pSoldier->pathing.usPathIndex == 0 )
{
// we can hack this surely! -- CJC
pSoldier->pathing.bPathStored = TRUE;
pSoldier->pathing.sFinalDestination = sGoToGrid;
pSoldier->pathing.usPathDataSize = sLoop + 1;
}
#ifdef DEBUGDECISIONS
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_BETAVERSION, L"%d to %d with %d APs left", pSoldier->ubID, sGoToGrid, pSoldier->bActionPoints );
#endif
return( sGoToGrid );
}
}
+4 -4
View File
@@ -244,7 +244,7 @@ void PossiblyMakeThisEnemyChosenOne( SOLDIERTYPE * pSoldier )
}
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
{
BOOLEAN fFoundRoute = FALSE;
INT8 bSlot;
@@ -307,7 +307,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
if (bPanicTrigger == -1)
{
// augh!
return( -1 );
return( AI_ACTION_INVALID );
}
sPanicTriggerGridNo = gTacticalStatus.sPanicTriggerGridNo[ bPanicTrigger ];
@@ -430,7 +430,7 @@ INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove)
}
// no action decided
return(-1);
return(AI_ACTION_INVALID);
}
void InitPanicSystem( void )
@@ -534,7 +534,7 @@ BOOLEAN NeedToRadioAboutPanicTrigger( void )
#define STAIRCASE_GRIDNO 12067
#define STAIRCASE_DIRECTION 0
INT8 HeadForTheStairCase( SOLDIERTYPE * pSoldier )
ActionType HeadForTheStairCase( SOLDIERTYPE * pSoldier )
{
UNDERGROUND_SECTORINFO * pBasementInfo;
+372
View File
@@ -0,0 +1,372 @@
#include "UtilityAI.h"
#include "UtilityAI_ResponseCurve.h"
#include "Overhead Types.h"
#include "Soldier Control.h"
#include "Isometric Utils.h"
#include "soldier profile type.h"
#include "AIInternals.h"
#include "Render Fun.h"
namespace UtilityAI
{
typedef enum DecisionInput
{
MyHealth,
MyStamina,
MyStance,
MyMorale,
MyCurrentWeaponRange,
DistanceFromMe,
HealthOfTarget,
AllyCount,
EnemyCount,
AllyEnemyRatio,
EnemiesNearTarget,
AlliesNearTarget,
HaveGasmaskInInventory,
HaveNVGInInventory,
HasKnife,
HasThrowingKnife,
HasSmokeGrenade,
IamInSmoke,
IamInTearGas,
IamInMustardGas, // Also fire & creature gas
NearSmoke,
NearTearGas,
NearMustardGas,
InShock,
InsideRoom,
NightTime,
InLightAtNight,
TakenLargeHit,
HasTrait_AutoWeapons,
HasTrait_HeavyWeapons,
HasTrait_Sniper,
HasTrait_Ranger,
HasTrait_Gunslinger,
HasTrait_MartialArts,
HasTrait_SquadLeader,
HasTrait_Technician,
HasTrait_Doctor,
HasTrait_Ambidextrous,
HasTrait_Melee,
HasTrait_Throwing,
HasTrait_NightOps,
HasTrait_Stealthy,
HasTrait_Athletics,
HasTrait_Bodybuilding,
HasTrait_Demolitions,
HasTrait_Teaching,
HasTrait_Scouting,
HasTrait_Covert,
HasTrait_RadioOperator,
HasTrait_Snitch,
HasTrait_Survival,
HasTraitOld_Lockpicking,
HasTraitOld_HandToHand,
HasTraitOld_Electronics,
HasTraitOld_NightOps,
HasTraitOld_Throwing,
HasTraitOld_Teaching,
HasTraitOld_HeavyWeapons,
HasTraitOld_AutoWeapons,
HasTraitOld_Stealthy,
HasTraitOld_Ambidextrous,
HasTraitOld_Thief,
HasTraitOld_MartialArts,
HasTraitOld_Knifing,
HasTraitOld_Sniper,
HasTraitOld_Camouflaged,
};
struct Consideration
{
DecisionInput input;
ResponseCurve curve;
SoldierID target;
INT32 targetLocation;
};
struct DecisionScoreEvaluator
{
ActionType Action;
float priorityWeight;
std::string description;
std::vector<Consideration> considerations;
};
struct DecisionScore
{
float score;
size_t idx; // Index into DM::decisions
bool operator<(const DecisionScore& a) const
{
return score < a.score;
}
bool operator>(const DecisionScore& a) const
{
return score > a.score;
}
bool operator==(const DecisionScore& a) const
{
return score == a.score;
}
};
struct DecisionMaker
{
std::vector<DecisionScoreEvaluator> decisions;
std::vector<DecisionScore> scores;
};
static float GetTrait(DecisionInput input, SOLDIERTYPE* soldier)
{
switch ( input )
{
case HasTrait_AutoWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AUTO_WEAPONS_NT));
case HasTrait_HeavyWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, HEAVY_WEAPONS_NT));
case HasTrait_Sniper:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SNIPER_NT));
case HasTrait_Ranger:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, RANGER_NT));
case HasTrait_Gunslinger:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, GUNSLINGER_NT));
case HasTrait_MartialArts:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, MARTIAL_ARTS_NT));
case HasTrait_SquadLeader:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SQUADLEADER_NT));
case HasTrait_Technician:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, TECHNICIAN_NT));
case HasTrait_Doctor:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, DOCTOR_NT));
case HasTrait_Ambidextrous:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AMBIDEXTROUS_NT));
case HasTrait_Melee:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, MELEE_NT));
case HasTrait_Throwing:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, THROWING_NT));
case HasTrait_NightOps:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, NIGHT_OPS_NT));
case HasTrait_Stealthy:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, STEALTHY_NT));
case HasTrait_Athletics:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, ATHLETICS_NT));
case HasTrait_Bodybuilding:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, BODYBUILDING_NT));
case HasTrait_Demolitions:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, DEMOLITIONS_NT));
case HasTrait_Teaching:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, TEACHING_NT));
case HasTrait_Scouting:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SCOUTING_NT));
case HasTrait_Covert:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, COVERT_NT));
case HasTrait_RadioOperator:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, RADIO_OPERATOR_NT));
case HasTrait_Snitch:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SNITCH_NT));
case HasTrait_Survival:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, SURVIVAL_NT));
case HasTraitOld_Lockpicking:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, LOCKPICKING_OT));
case HasTraitOld_HandToHand:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, HANDTOHAND_OT));
case HasTraitOld_Electronics:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, ELECTRONICS_OT));
case HasTraitOld_NightOps:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, NIGHTOPS_OT));
case HasTraitOld_Throwing:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, THROWING_OT));
case HasTraitOld_Teaching:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, TEACHING_OT));
case HasTraitOld_HeavyWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, HEAVY_WEAPS_OT));
case HasTraitOld_AutoWeapons:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AUTO_WEAPS_OT));
case HasTraitOld_Stealthy:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, STEALTHY_OT));
case HasTraitOld_Ambidextrous:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, AMBIDEXT_OT));
case HasTraitOld_Thief:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, THIEF_OT));
case HasTraitOld_MartialArts:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, MARTIALARTS_OT));
case HasTraitOld_Knifing:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, KNIFING_OT));
case HasTraitOld_Sniper:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, PROF_SNIPER_OT));
case HasTraitOld_Camouflaged:
return static_cast<float>(HAS_SKILL_TRAIT(soldier, CAMOUFLAGED_OT));
}
return 0.0f;
}
float GetInputValue(DecisionInput input, SOLDIERTYPE* me, SoldierID target, INT32 targetLocation)
{
switch ( input )
{
case MyHealth:
{
const auto health = static_cast<float>(me->stats.bLife);
const auto min = 0.0f;
const auto max = static_cast<float>(me->stats.bLifeMax);
return NormalizeInput(health, min, max);
}
case MyMorale:
{
const auto morale = static_cast<float>(me->aiData.bAIMorale);
const auto min = 0.0f;
const auto max = static_cast<float>(MORALE_FEARLESS);
return NormalizeInput(morale, min, max);
}
case HaveGasmaskInInventory:
{
const auto gasmaskInvSlot = FindGasMask(me);
if ( gasmaskInvSlot != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case HasKnife:
{
const auto bWeaponIn = FindAIUsableObjClass(me, (IC_BLADE));
if ( bWeaponIn != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case HasThrowingKnife:
{
const auto bWeaponIn = FindAIUsableObjClass(me, (IC_THROWING_KNIFE));
if ( bWeaponIn != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case HasSmokeGrenade:
{
const auto bGrenadeIn = FindThrowableGrenade(me, EXPLOSV_SMOKE);
if ( bGrenadeIn != NO_SLOT ) { return 1.0f; }
else { return 0.0f; }
}
case DistanceFromMe:
{
const auto distance = PythSpacesAway(me->sGridNo, targetLocation);
}
case IamInSmoke:
return static_cast<float>(InSmoke(me, me->sGridNo));
case IamInTearGas:
return static_cast<float>(InTearGas(me, me->sGridNo));
case IamInMustardGas:
return static_cast<float>(InMustardGas(me, me->sGridNo));
case NearSmoke:
case NearTearGas:
case NearMustardGas:
case InShock:
case InsideRoom:
return static_cast<float>(InARoom(me->sGridNo, NULL));
case NightTime:
case InLightAtNight:
case TakenLargeHit:
return static_cast<float>(me->TakenLargeHit());
case HasTrait_AutoWeapons:
case HasTrait_HeavyWeapons:
case HasTrait_Sniper:
case HasTrait_Ranger:
case HasTrait_Gunslinger:
case HasTrait_MartialArts:
case HasTrait_SquadLeader:
case HasTrait_Technician:
case HasTrait_Doctor:
case HasTrait_Ambidextrous:
case HasTrait_Melee:
case HasTrait_Throwing:
case HasTrait_NightOps:
case HasTrait_Stealthy:
case HasTrait_Athletics:
case HasTrait_Bodybuilding:
case HasTrait_Demolitions:
case HasTrait_Teaching:
case HasTrait_Scouting:
case HasTrait_Covert:
case HasTrait_RadioOperator:
case HasTrait_Snitch:
case HasTrait_Survival:
case HasTraitOld_Lockpicking:
case HasTraitOld_HandToHand:
case HasTraitOld_Electronics:
case HasTraitOld_NightOps:
case HasTraitOld_Throwing:
case HasTraitOld_Teaching:
case HasTraitOld_HeavyWeapons:
case HasTraitOld_AutoWeapons:
case HasTraitOld_Stealthy:
case HasTraitOld_Ambidextrous:
case HasTraitOld_Thief:
case HasTraitOld_MartialArts:
case HasTraitOld_Knifing:
case HasTraitOld_Sniper:
case HasTraitOld_Camouflaged:
return GetTrait(input, me);
}
}
float ScoreDSE(DecisionScoreEvaluator DSE, SOLDIERTYPE* me)
{
float finalScore = 1.0f;
for ( const auto& consideration : DSE.considerations )
{
if ( finalScore <= 0.0f ) { break; }
float curveInput = GetInputValue(consideration.input, me, consideration.target, consideration.targetLocation);
float considerationScore = CalculateResponse(curveInput, consideration.curve);
finalScore *= considerationScore;
}
// Add consideration compensation
const auto modificationFactor = 1 - 1 / (DSE.considerations.size());
const auto makeUpValue = (1 - finalScore) * modificationFactor;
finalScore += finalScore * makeUpValue;
// Add DSE priority weight
finalScore *= DSE.priorityWeight;
return finalScore;
}
void ScoreAllDecisions(DecisionMaker DM, SOLDIERTYPE* me)
{
size_t idx = 0;
for ( const auto& decision : DM.decisions )
{
DM.scores[idx].score = ScoreDSE(decision, me);
DM.scores[idx].idx = idx;
++idx;
}
// Sort all scores from best to last
std::sort(DM.scores.begin(), DM.scores.end(), std::greater<DecisionScore>());
}
ActionType SelectScoredAction(DecisionMaker DM)
{
// Select highest for now. TODO weighted random choice
const auto idx = DM.scores[0].idx;
const auto action = DM.decisions[idx].Action;
return action;
}
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
//void ScoreAllDecisions(DecisionMaker DM, SOLDIERTYPE* me);
//ActionType SelectScoredAction(DecisionMaker DM);
+57
View File
@@ -0,0 +1,57 @@
#include "UtilityAI_ResponseCurve.h"
#include <cmath>
#include <algorithm>
namespace UtilityAI
{
float NormalizeInput(float value, float min, float max)
{
const float normalizedValue = (value - min) / (max - min);
return normalizedValue;
}
float CalculateResponse(float x, ResponseCurve curve)
{
const auto min = curve.min;
const auto max = curve.max;
const auto type = curve.type;
const auto m = curve.m;
const auto k = curve.k;
const auto b = curve.b;
const auto c = curve.c;
float input = std::clamp(x, min, max);
input = NormalizeInput(input, min, max);
float y = 0.0f;
// Polynomial
float n;
// Logistic
const float e = 2.71828f; // Euler's number. I think? Lecture slides did not explain if this is what it's supposed to be.
const float exponent = -input + c;
const float denominator = 1.0f + 1000.0f * e * pow(m, exponent);
switch ( type )
{
case Step:
if ( input != 0.0f ) { return 1.0f; }
else { return 0.0f; }
case Linear:
case Polynomial:
n = input - c;
y = m * pow(n, k) + b;
break;
case Logistic:
y = (k / denominator) + b;
break;
//case Logit: // Not implemented yet
//break;
}
y = std::clamp(y, 0.0f, 1.0f);
return y;
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
typedef enum
{
Step,
Linear,
Polynomial,
Logistic,
Logit
} ResponseCurveType;
struct ResponseCurve
{
ResponseCurveType type;
float m;
float k;
float b;
float c;
float min;
float max;
};
float NormalizeInput(float value, float min, float max);
float CalculateResponse(float x, ResponseCurve curve);
+1 -1
View File
@@ -33,7 +33,7 @@
#include "Text.h"
extern BOOLEAN gfHiddenInterrupt;
extern void LogDecideInfo(SOLDIERTYPE *pSoldier);
extern void LogDecideInfo(SOLDIERTYPE *pSoldier, bool doLog = true);
extern STR8 gStr8AlertStatus[];
extern STR8 gStr8Attitude[];
+39 -12
View File
@@ -12,10 +12,6 @@
extern INT16 gubAIPathCosts[19][19];
#define AI_PATHCOST_RADIUS 9
extern BOOLEAN gfDisplayCoverValues;
//extern INT16 gsCoverValue[WORLD_MAX];
extern INT16 * gsCoverValue;
// AI actions
enum CreatureCalls
@@ -110,7 +106,10 @@ typedef enum
AI_ACTION_DOCTOR_SELF, // added by Flugente: AI-ONLY! bandage/surgery on self. DO NOT USE THIS FOR MERCS!!!
AI_ACTION_SELFDETONATE, // added by Flugente: blow up an explosive in own inventory
AI_ACTION_STOP_MEDIC, // sevenfm: stop giving aid animation
AI_ACTION_LAST = AI_ACTION_STOP_MEDIC
AI_ACTION_DRINK_CANTEEN, // sevenfm: drink from canteen in inventory
AI_ACTION_HANDLE_ITEM, // sevenfm: use item in hand
AI_ACTION_PLANT_BOMB, // sevenfm: plant bomb using item in hand
AI_ACTION_INVALID
} ActionType;
@@ -175,21 +174,36 @@ void CheckForChangingOrders(SOLDIERTYPE *pSoldier );
INT8 ClosestPanicTrigger( SOLDIERTYPE * pSoldier );
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID *pubOpponentID = NULL);
INT32 ClosestKnownOpponent(SOLDIERTYPE *pSoldier, INT32 * psGridNo, INT8 * pbLevel, SoldierID *pubOpponentID = NULL, INT32 * distanceInCellCoords = NULL);
INT32 ClosestPC( SOLDIERTYPE *pSoldier, INT32 * psDistance );
INT32 ClosestUnDisguisedPC( SOLDIERTYPE *pSoldier, INT32 * psDistance ); // Flugente: like ClosestPC(...), but does not account for covert or not visible mercs
BOOLEAN CanAutoBandage( BOOLEAN fDoFullCheck );
void DebugAI( STR szOutput );
enum { AI_MSG_START, AI_MSG_DECIDE, AI_MSG_INFO, AI_MSG_TOPIC };
void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, INT8 bAction = -1);
void DebugAI(INT8 bMsgType, SOLDIERTYPE *pSoldier, STR szOutput, bool doLog = true, INT8 bAction = -1);
void DebugQuestInfo(STR szOutput);
INT8 DecideAction(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
INT8 DecideActionEscort(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreen(SOLDIERTYPE *pSoldier);
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
INT8 DecideActionYellow(SOLDIERTYPE *pSoldier);
INT8 DecideActionRed(SOLDIERTYPE *pSoldier);
INT8 DecideActionBlack(SOLDIERTYPE *pSoldier);
INT8 DecideActionGreenBoxer(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackBoxer(SOLDIERTYPE* pSoldier);
INT8 DecideActionGreenCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionYellowCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionRedCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackCivilian(SOLDIERTYPE* pSoldier);
INT8 DecideActionGreenSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionYellowSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionRedSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackSoldier(SOLDIERTYPE* pSoldier);
INT8 DecideActionGreenRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionYellowRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionRedRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackRobot(SOLDIERTYPE* pSoldier);
INT8 DecideActionBlackSoldierUtilityAI(SOLDIERTYPE* pSoldier);
INT16 DistanceToClosestFriend( SOLDIERTYPE * pSoldier );
@@ -199,11 +213,12 @@ void EndAIGuysTurn( SOLDIERTYPE *pSoldier );
INT8 ExecuteAction(SOLDIERTYPE *pSoldier);
INT32 FindAdjacentSpotBeside(SOLDIERTYPE *pSoldier, INT32 sGridNo);
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter);
INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *pPercentBetter, INT32 targetGridNo = NOWHERE, bool ignoreSearchRange = false);
INT32 FindClosestDoor( SOLDIERTYPE * pSoldier );
INT32 FindNearbyPointOnEdgeOfMap( SOLDIERTYPE * pSoldier, INT8 * pbDirection );
INT32 FindNearestEdgePoint( INT32 sGridNo );
INT32 FindNearestPassableSpot( INT32 sGridNo, UINT8 usSearchRadius = 5 );
BOOLEAN FindFenceAroundSpot(INT32 sSpot);
//Kris: Added these as I need specific searches on certain sides.
enum
@@ -244,7 +259,7 @@ void ManChecksOnFriends(SOLDIERTYPE *pSoldier);
void NewDest(SOLDIERTYPE *pSoldier, INT32 sGridNo);
INT32 NextPatrolPoint(SOLDIERTYPE *pSoldier);
INT8 PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
ActionType PanicAI(SOLDIERTYPE *pSoldier, UINT8 ubCanMove);
void HaltMoveForSoldierOutOfPoints(SOLDIERTYPE *pSoldier);
INT32 RandDestWithinRange(SOLDIERTYPE *pSoldier);
@@ -288,6 +303,7 @@ SoldierID GetClosestMedicSoldierID( SOLDIERTYPE * pSoldier, INT16 aRange, UINT8
// sevenfm:
BOOLEAN NightLight(void);
BOOLEAN DuskLight(void);
BOOLEAN FindClosestVisibleSmoke(SOLDIERTYPE* pSoldier, INT32& sSpot, INT8& bLevel, BOOLEAN fOnlyGas);
BOOLEAN InSmokeNearby(INT32 sGridNo, INT8 bLevel);
INT16 MaxNormalVisionDistance( void );
UINT8 MinFlankDirections( SOLDIERTYPE *pSoldier );
@@ -300,6 +316,13 @@ UINT8 CountFriendsBlack( SOLDIERTYPE *pSoldier, INT32 sClosestOpponent = NOWHERE
UINT16 CountTeamUnderAttack(INT8 bTeam, INT32 sGridNo, INT16 sDistance);
UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 sDistance);
UINT16 CountPublicKnownEnemies(SOLDIERTYPE *pSoldier);
UINT8 CountKnownEnemies(SOLDIERTYPE* pSoldier, INT32 sSpot, INT16 sDistance, INT8 bLevel = HEARD_THIS_TURN);
UINT8 CountKnownEnemiesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom);
UINT8 CountFriendsInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoom);
INT32 CountCorpsesInRoom(SOLDIERTYPE* pSoldier, UINT16 usRoomNo, INT8 bLevel);
UINT16 RoomNo(INT32 sSpot);
BOOLEAN SameRoom(INT32 sSpot1, INT32 sSpot2);
BOOLEAN CheckWindow(INT32 sSpot, UINT8 ubDirection, BOOLEAN fAllowClosed);
UINT8 SectorCurfew(BOOLEAN fNight);
UINT8 TeamPercentKilled(INT8 bTeam);
@@ -420,6 +443,10 @@ INT8 KnownPublicLevel(UINT8 bTeam, SoldierID ubOpponentID);
// sevenfm: distance for tactical AI checks, roughly equal to normal day vision range
#define TACTICAL_RANGE (gGameExternalOptions.ubStraightSightRange * STRAIGHT_RATIO * 2)
#define TACTICAL_RANGE_CELL_COORDS TACTICAL_RANGE*CELL_X_SIZE
#define BOMB_DETECTION_RANGE (TACTICAL_RANGE / 4)
#define TACTICAL_RANGE_HALF (TACTICAL_RANGE / 2)
#define TACTICAL_RANGE_CLOSE (TACTICAL_RANGE / 4)
#define TACTICAL_RANGE_VERYCLOSE (TACTICAL_RANGE / 6)
#endif
+1 -64
View File
@@ -1319,7 +1319,7 @@ BOOLEAN FindFenceJumpDirection( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bStar
return( FALSE );
}
//Simply chooses a random gridno within valid boundaries (for dropping things in loaded sectors)
//Simply chooses a random gridno within valid boundaries (for dropping things in unloaded sectors)
INT32 RandomGridNo()
{
INT32 iMapXPos, iMapYPos, iMapIndex;
@@ -1332,69 +1332,6 @@ INT32 RandomGridNo()
return iMapIndex;
}
static bool pointLeftOfEdge(INT32 xp, INT32 yp, INT32 x1, INT32 y1, INT32 x2, INT32 y2)
{
//Test whether the point(xp, yp) lies on the left-hand side of the edge(x1, y1) - (x2, y2)
const auto D = (x2 - x1) * (yp - y1) - (xp - x1) * (y2 - y1);
return (D > 0);
}
INT32 RandomGridNoUnloadedSector(INT32 worldRows, INT32 worldCols)
{
INT32 iMapIndex;
const auto centerHeight = gpWorldLevelDataAutoResolve[gMapInformationAutoResolve.sCenterGridNo].sHeight;
const auto CenterWorldX = (WORLD_ROWS) / 2 * CELL_X_SIZE;
const auto CenterWorldY = (WORLD_COLS) / 2 * CELL_Y_SIZE;
// Sector corners are shifted inwards a bit to limit the area to be slightly smaller than actual sector.
// It fixed a few annyoing edge cases where a tile would evaluate to being inside the map, but it was juuust out of reach in actual gameplay
const auto TLX = 2*CELL_X_SIZE; // Shift X-coordinate by one tile inwards from actual edge.
const auto TLY = (WORLD_ROWS / 2) * CELL_X_SIZE;
const auto TRX = (WORLD_COLS / 2) * CELL_Y_SIZE;
const auto TRY = 2*CELL_X_SIZE; // Shift Y-coordinate by one tile inwards from actual edge
const auto BLX = ((WORLD_COLS / 2) * CELL_Y_SIZE);
const auto BLY = ((WORLD_ROWS-1) * CELL_Y_SIZE); // Shift Y-coordinate by one row inwards from actual edge
const auto BRX = ((WORLD_COLS-1) * CELL_Y_SIZE); // Shift X-coordinate by one row inwards from actual edge
const auto BRY = ((WORLD_ROWS / 2) * CELL_X_SIZE);
UINT8 randomGridHeight = 255;
do
{
randomGridHeight = 255; // Reset height to be tested
INT32 iMapXPos = Random(worldCols);
INT32 iMapYPos = Random(worldRows);
iMapIndex = iMapYPos * worldCols + iMapXPos;
// Discard gridno that is not within map bounds.
// Check point against world area's edges. If any edge test fails, it means point is not inside.
// Edge traverals goes counterclockwise, Top Right -> Top Left -> Bottom Left -> Bottom Right -> Top Right
const auto pX = iMapXPos * CELL_X_SIZE;
const auto pY = iMapYPos * CELL_Y_SIZE;
//TR->TL
if ( !pointLeftOfEdge(pX, pY, TLX, TLY, TRX, TRY) )
continue;
//TL->BL
if ( !pointLeftOfEdge(pX, pY, BLX, BLY, TLX, TLY) )
continue;
//BL->BR
if ( !pointLeftOfEdge(pX, pY, BRX, BRY, BLX, BLY) )
continue;
//BR->TR
if ( !pointLeftOfEdge(pX, pY, TRX, TRY, BRX, BRY) )
continue;
// Compare randomGridHeight with height of the center grid of the map, as long as the center of the map is on walkable height it will work properly
randomGridHeight = gpWorldLevelDataAutoResolve[iMapIndex].sHeight;
} while ( !(randomGridHeight == centerHeight) );
return iMapIndex;
}
// Flugente: is this gridno near a player merc?
BOOLEAN GridNoNearPlayerMercs( INT32 sGridNo, INT16 sRadius )
{
-1
View File
@@ -113,7 +113,6 @@ BOOLEAN FindFenceJumpDirection( SOLDIERTYPE *pSoldier, INT32 sGridNo, INT8 bStar
//Simply chooses a random gridno within valid boundaries (for dropping things in unloaded sectors)
INT32 RandomGridNo();
INT32 RandomGridNoUnloadedSector(INT32 worldRows, INT32 worldCols);
extern UINT32 guiForceRefreshMousePositionCalculation;
+166 -157
View File
@@ -23,6 +23,7 @@
#include "LogicalBodyTypes/BodyTypeDB.h"
#include "Utilities.h"
#include <Cheats.h>
UINT32 guiShieldGraphic = 0;
BOOLEAN fShieldGraphicInit = FALSE;
@@ -552,22 +553,25 @@ void ResetRenderParameters( );
void RenderRoomInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
#ifdef _DEBUG
//extern UINT8 gubFOVDebugInfoInfo[ WORLD_MAX ];
//extern UINT8 gubGridNoMarkers[ WORLD_MAX ];
extern UINT8 * gubFOVDebugInfoInfo;
extern UINT8 * gubGridNoMarkers;
extern UINT8 gubGridNoValue;
extern BOOLEAN gfDisplayCoverValues;
extern BOOLEAN gfDisplayGridNoVisibleValues = 0;
//extern INT16 gsCoverValue[ WORLD_MAX ];
extern INT16 * gsCoverValue;
extern INT16 gsBestCover;
void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
void RenderCoverDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
#endif
//extern UINT8 * gubFOVDebugInfoInfo;
//extern UINT8 * gubGridNoMarkers;
//extern UINT8 gubGridNoValue;
//extern BOOLEAN gfDisplayGridNoVisibleValues = 0;
//void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
//void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS );
extern INT16 gsBestCover;
INT32* gRenderDebugInfoValues = nullptr;
void RenderDebugInfo(INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS);
void ResetDebugInfoValues()
{
if (gRenderDebugInfoValues)
{
for (size_t i = 0; i < WORLD_MAX; i++)
{
gRenderDebugInfoValues[i] = 0x7FFFFFFF;
}
}
}
void DeleteFromWorld( UINT16 usTileIndex, UINT32 uiRenderTiles, UINT16 usIndex );
@@ -3272,23 +3276,10 @@ UINT32 cnt = 0;
RenderRoomInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
#ifdef _DEBUG
if( gRenderFlags&RENDER_FLAG_FOVDEBUG )
if (DEBUG_CHEAT_LEVEL() && gTacticalStatus.Team[OUR_TEAM].bTeamActive)
{
RenderFOVDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
RenderDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS);
}
else if (gfDisplayCoverValues)
{
RenderCoverDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
else if (gfDisplayGridNoVisibleValues)
{
RenderGridNoVisibleDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
#endif
//#endif
//RenderStaticWorldRect( gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y );
//AddBaseDirtyRect(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y );
@@ -8014,6 +8005,10 @@ void RenderRoomInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPoi
#ifdef _DEBUG
// From fov.cpp
extern UINT8* gubGridNoMarkers;
extern UINT8 gubGridNoValue;
extern UINT8* gubFOVDebugInfoInfo;
void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS )
{
@@ -8124,114 +8119,7 @@ void RenderFOVDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStar
}
void RenderCoverDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS )
{
INT8 bXOddFlag = 0;
INT16 sAnchorPosX_M, sAnchorPosY_M;
INT16 sAnchorPosX_S, sAnchorPosY_S;
INT16 sTempPosX_M, sTempPosY_M;
INT16 sTempPosX_S, sTempPosY_S;
BOOLEAN fEndRenderRow = FALSE, fEndRenderCol = FALSE;
UINT16 usTileIndex;
INT16 sX, sY;
UINT32 uiDestPitchBYTES;
UINT8 *pDestBuf;
// Begin Render Loop
sAnchorPosX_M = sStartPointX_M;
sAnchorPosY_M = sStartPointY_M;
sAnchorPosX_S = sStartPointX_S;
sAnchorPosY_S = sStartPointY_S;
pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES );
do
{
fEndRenderRow = FALSE;
sTempPosX_M = sAnchorPosX_M;
sTempPosY_M = sAnchorPosY_M;
sTempPosX_S = sAnchorPosX_S;
sTempPosY_S = sAnchorPosY_S;
if(bXOddFlag > 0)
sTempPosX_S += 20;
do
{
usTileIndex=FASTMAPROWCOLTOPOS( sTempPosY_M, sTempPosX_M );
if ( usTileIndex < GRIDSIZE )
{
sX = sTempPosX_S + ( WORLD_TILE_X / 2 ) - 5;
sY = sTempPosY_S + ( WORLD_TILE_Y / 2 ) - 5;
// Adjust for interface level
sY -= gpWorldLevelData[ usTileIndex ].sHeight;
sY += gsRenderHeight;
if (gsCoverValue[ usTileIndex] != 0x7F7F)
{
SetFont( SMALLCOMPFONT );
SetFontDestBuffer( FRAME_BUFFER , 0, 0, SCREEN_WIDTH, gsVIEWPORT_END_Y, FALSE );
if (usTileIndex == gsBestCover)
{
SetFontForeground( FONT_MCOLOR_RED );
}
else if (gsCoverValue[ usTileIndex ] < 0)
{
SetFontForeground( FONT_MCOLOR_WHITE );
}
else
{
SetFontForeground( FONT_GRAY3 );
}
mprintf_buffer( pDestBuf, uiDestPitchBYTES, TINYFONT1, sX, sY , L"%d", gsCoverValue[ usTileIndex ] );
SetFontDestBuffer( FRAME_BUFFER , 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE );
}
}
sTempPosX_S += 40;
sTempPosX_M ++;
sTempPosY_M --;
if ( sTempPosX_S >= sEndXS )
{
fEndRenderRow = TRUE;
}
} while( !fEndRenderRow );
if ( bXOddFlag > 0 )
{
sAnchorPosY_M ++;
}
else
{
sAnchorPosX_M ++;
}
bXOddFlag = !bXOddFlag;
sAnchorPosY_S += 10;
if ( sAnchorPosY_S >= sEndYS )
{
fEndRenderCol = TRUE;
}
}
while( !fEndRenderCol );
UnLockVideoSurface( FRAME_BUFFER );
}
// This one just renders tile gridnos. Not much use nowadays
void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS )
{
INT8 bXOddFlag = 0;
@@ -8334,9 +8222,148 @@ void RenderGridNoVisibleDebugInfo( INT16 sStartPointX_M, INT16 sStartPointY_M, I
}
void RenderFOVDebug()
{
RenderFOVDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS);
}
void RenderGridNoVisibleDebug()
{
RenderGridNoVisibleDebugInfo(gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS);
}
#endif
void RenderDebugInfo(INT16 sStartPointX_M, INT16 sStartPointY_M, INT16 sStartPointX_S, INT16 sStartPointY_S, INT16 sEndXS, INT16 sEndYS)
{
INT8 bXOddFlag = 0;
INT16 sTempPosX_M, sTempPosY_M;
INT16 sTempPosX_S, sTempPosY_S;
BOOLEAN fEndRenderRow = FALSE, fEndRenderCol = FALSE;
// Begin Render Loop
INT16 sAnchorPosX_M = sStartPointX_M;
INT16 sAnchorPosY_M = sStartPointY_M;
INT16 sAnchorPosX_S = sStartPointX_S;
INT16 sAnchorPosY_S = sStartPointY_S;
UINT32 uiDestPitchBYTES;
UINT8* pDestBuf = LockVideoSurface(FRAME_BUFFER, &uiDestPitchBYTES);
const auto mode = gRenderDebugInfoMode;
do
{
fEndRenderRow = FALSE;
sTempPosX_M = sAnchorPosX_M;
sTempPosY_M = sAnchorPosY_M;
sTempPosX_S = sAnchorPosX_S;
sTempPosY_S = sAnchorPosY_S;
if (bXOddFlag > 0)
sTempPosX_S += 20;
do
{
UINT16 usTileIndex = FASTMAPROWCOLTOPOS(sTempPosY_M, sTempPosX_M);
if (usTileIndex < GRIDSIZE)
{
INT16 sX = sTempPosX_S + (WORLD_TILE_X / 2) - 5;
INT16 sY = sTempPosY_S + (WORLD_TILE_Y / 2) - 5;
// Adjust for interface level
sY -= gpWorldLevelData[usTileIndex].sHeight;
sY += gsRenderHeight;
if (gRenderDebugInfoValues[usTileIndex] != 0x7FFFFFFF)
{
SetFont(SMALLCOMPFONT);
SetFontDestBuffer(FRAME_BUFFER, 0, 0, SCREEN_WIDTH, gsVIEWPORT_END_Y, FALSE);
////////////////////////////
// Debug mode specific setup
switch (mode)
{
case DEBUG_PATHFINDING:
if (gRenderDebugInfoValues[usTileIndex] < 0)
{
SetFontForeground(FONT_LTRED);
}
else
{
SetFontForeground(FONT_LTGREEN);
}
break;
case DEBUG_THREATVALUE:
//TODO implement
goto exit_loop;
break;
case DEBUG_COVERVALUE:
if (usTileIndex == gsBestCover)
{
SetFontForeground(FONT_YELLOW);
}
else if (gRenderDebugInfoValues[usTileIndex] < 0)
{
SetFontForeground(FONT_LTRED);
}
else
{
SetFontForeground(FONT_LTGREEN);
}
break;
default:
goto exit_loop;
break;
}
////////////////////////////
mprintf_buffer(pDestBuf, uiDestPitchBYTES, TINYFONT1, sX, sY, L"%d", gRenderDebugInfoValues[usTileIndex]);
SetFontDestBuffer(FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE);
}
}
sTempPosX_S += 40;
sTempPosX_M++;
sTempPosY_M--;
if (sTempPosX_S >= sEndXS)
{
fEndRenderRow = TRUE;
}
} while (!fEndRenderRow);
if (bXOddFlag > 0)
{
sAnchorPosY_M++;
}
else
{
sAnchorPosX_M++;
}
bXOddFlag = !bXOddFlag;
sAnchorPosY_S += 10;
if (sAnchorPosY_S >= sEndYS)
{
fEndRenderCol = TRUE;
}
} while (!fEndRenderCol);
exit_loop:
UnLockVideoSurface(FRAME_BUFFER);
}
void ExamineZBufferRect( INT16 sLeft, INT16 sTop, INT16 sRight, INT16 sBottom)
{
CalcRenderParameters( sLeft, sTop, sRight, sBottom );
@@ -8997,21 +9024,3 @@ void SetRenderCenter( INT16 sNewX, INT16 sNewY )
gfScrollInertia = FALSE;
}
#ifdef _DEBUG
void RenderFOVDebug( )
{
RenderFOVDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
void RenderCoverDebug( )
{
RenderCoverDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
static void RenderGridNoVisibleDebug( )
{
RenderGridNoVisibleDebugInfo( gsStartPointX_M, gsStartPointY_M, gsStartPointX_S, gsStartPointY_S, gsEndXS, gsEndYS );
}
#endif
+11 -1
View File
@@ -216,6 +216,16 @@ void SetRenderCenter( INT16 sNewX, INT16 sNewY );
#ifdef _DEBUG
void RenderFOVDebug( );
#endif
enum RenderDebugInfoModes
{
DEBUG_PATHFINDING,
DEBUG_THREATVALUE,
DEBUG_COVERVALUE,
DEBUG_OFF
};
void ResetDebugInfoValues();
extern INT32* gRenderDebugInfoValues;
BOOLEAN Zero8BPPDataTo16BPPBufferTransparent( UINT16 *pBuffer, UINT32 uiDestPitchBYTES, HVOBJECT hSrcVObject, INT32 iX, INT32 iY, UINT16 usIndex );
BOOLEAN Blt8BPPDataTo16BPPBufferTransZIncClipZSameZBurnsThrough( UINT16 *pBuffer, UINT32 uiDestPitchBYTES, UINT16 *pZBuffer, UINT16 usZValue, HVOBJECT hSrcVObject, INT32 iX, INT32 iY, UINT16 usIndex, SGPRect *clipregion, INT16 sZStripIndex );
@@ -227,4 +237,4 @@ BOOLEAN Blt8BPPDataTo16BPPBufferTransZIncObscureClip(UINT16 *pBuffer, UINT32 uiD
BOOLEAN Blt8BPPDataTo16BPPBufferTransZTransShadowIncObscureClip(UINT16 *pBuffer, UINT32 uiDestPitchBYTES, UINT16 *pZBuffer, UINT16 usZValue, HVOBJECT hSrcVObject, INT32 iX, INT32 iY, UINT16 usIndex, SGPRect *clipregion, INT16 sZIndex, UINT16 *p16BPPPalette, BOOLEAN fIgnoreShadows = FALSE);
BOOLEAN Blt8BPPDataTo16BPPBufferTransZTransShadowIncObscureClipAlpha(UINT16 *pBuffer, UINT32 uiDestPitchBYTES, UINT16 *pZBuffer, UINT16 usZValue, HVOBJECT hSrcVObject, HVOBJECT hAlphaVObject, INT32 iX, INT32 iY, UINT16 usIndex, SGPRect *clipregion, INT16 sZIndex, UINT16 *p16BPPPalette, BOOLEAN fIgnoreShadows = FALSE);
#endif
#endif
-28
View File
@@ -1428,7 +1428,6 @@ STRUCTURE * SwapStructureForPartnerForcingGraphicalChange( INT32 sGridNo, STRUCT
}
#endif
// Finds the first structure with matching flag
STRUCTURE * FindStructure( INT32 sGridNo, UINT32 fFlags )
{
// finds a structure that matches any of the given flags
@@ -1452,33 +1451,6 @@ STRUCTURE * FindStructure( INT32 sGridNo, UINT32 fFlags )
return( NULL );
}
// Finds the last structure with matching flag
STRUCTURE* FindLastStructure(INT32 sGridNo, UINT32 fFlags)
{
// finds a structure that matches any of the given flags
STRUCTURE* pCurrent;
STRUCTURE* pLastMatch = nullptr;
//bug fix for win98 crash when traveling between sectors
if ( TileIsOutOfBounds(sGridNo) )
{
return(NULL);
}
pCurrent = gpWorldLevelData[sGridNo].pStructureHead;
while ( pCurrent != NULL )
{
if ( (pCurrent->fFlags & fFlags) != 0 )
{
pLastMatch = pCurrent;
//return(pCurrent);
}
pCurrent = pCurrent->pNext;
}
return(pLastMatch);
}
STRUCTURE * FindNextStructure( STRUCTURE * pStructure, UINT32 fFlags )
{
STRUCTURE * pCurrent;
-1
View File
@@ -47,7 +47,6 @@ BOOLEAN DeleteStructureFromWorld( STRUCTURE * pStructure );
// functions to find a structure in a location
//
STRUCTURE * FindStructure( INT32 sGridNo, UINT32 fFlags );
STRUCTURE* FindLastStructure(INT32 sGridNo, UINT32 fFlags);
STRUCTURE * FindNextStructure( STRUCTURE * pStructure, UINT32 fFlags );
STRUCTURE * FindStructureByID( INT32 sGridNo, UINT16 usStructureID );
STRUCTURE * FindBaseStructure( STRUCTURE * pStructure );
+8 -299
View File
@@ -49,6 +49,7 @@
#include "Button Defines.h"
#include "Animation Data.h"
#endif
#include <Cheats.h>
#define SET_MOVEMENTCOST( a, b, c, d ) ( ( gubWorldMovementCosts[ a ][ b ][ c ] < d ) ? ( gubWorldMovementCosts[ a ][ b ][ c ] = d ) : 0 );
@@ -84,7 +85,6 @@ extern UINT8 *gubFOVDebugInfoInfo;
extern INT16 gsFullTileDirections[MAX_FULLTILE_DIRECTIONS];
extern INT32 dirDelta[8];
extern INT16 DirIncrementer[8];
extern INT16 *gsCoverValue;
extern INT32 gsTempActionGridNo;
extern INT32 gsOverItemsGridNo;
extern INT32 gsOutOfRangeGridNo;
@@ -138,8 +138,7 @@ void SaveMapLights( HWFILE hfile );
void LoadMapLights( INT8 **hBuffer );
// Global Variables
MAP_ELEMENT* gpWorldLevelData;
MAP_ELEMENT *gpWorldLevelDataAutoResolve;
MAP_ELEMENT *gpWorldLevelData;
INT32 *gpDirtyData;
UINT32 gSurfaceMemUsage;
//UINT8 gubWorldMovementCosts[ WORLD_MAX ][MAXDIR][2];
@@ -317,8 +316,6 @@ void DeinitializeWorld()
TrashWorld();
if(gubGridNoMarkers)
MemFree(gubGridNoMarkers);
if(gsCoverValue)
MemFree(gsCoverValue);
if(gubBuildingInfo)
MemFree(gubBuildingInfo);
if(gusWorldRoomInfo)
@@ -3313,296 +3310,6 @@ BOOLEAN LoadWorld(const STR8 puiFilename, FLOAT* pMajorMapVersion, UINT8* pMinor
return(TRUE);
}
// A stub LoadWorld function so we can read a sector's tile height values into gpWorldLevelDataAutoResolve[].sHeight and entrypoint gridnos into gMapInformationAutoResolve
// They are needed when placing corpses and dropped items into a random gridno in an unloaded sector when we're finished with autoresolve battle.
BOOLEAN LoadWorldInfoForAutoResolve(const STR8 puiFilename, INT32& iRowSize, INT32& iColSize)
{
HWFILE hfile;
FLOAT dMajorMapVersion;
UINT8 ubMinorMapVersion;
UINT32 uiFlags;
UINT32 uiBytesRead;
UINT32 uiFileSize;
INT32 i, cnt, cnt2;
UINT8 ubType;
CHAR8 aFilename[2 * FILENAME_BUFLEN];
UINT8 ubCombine;
UINT8(*bCounts)[8] = NULL;
INT8* pBuffer;
INT8* pBufferHead;
// Append exension to filename!
if ( gfForceLoad )
sprintf(aFilename, "MAPS\\%s", gzForceLoadFile);
else
sprintf(aFilename, "MAPS\\%s", puiFilename);
// Open file
hfile = FileOpen(aFilename, FILE_ACCESS_READ);
if ( !hfile )
{
#ifndef JA2EDITOR
SET_ERROR("Could not load map file %S", aFilename);
#endif
return(FALSE);
}
// Get the file size and alloc one huge buffer for it. We will use this buffer to transfer all of the data from.
uiFileSize = FileGetSize(hfile);
pBuffer = (INT8*)MemAlloc(uiFileSize);
pBufferHead = pBuffer;
FileRead(hfile, pBuffer, uiFileSize, &uiBytesRead);
FileClose(hfile);
// Read JA2 Version ID
LOADDATA(&dMajorMapVersion, pBuffer, sizeof(FLOAT));
LOADDATA(&ubMinorMapVersion, pBuffer, sizeof(UINT8));
iRowSize = OLD_WORLD_ROWS;
iColSize = OLD_WORLD_COLS;
if ( dMajorMapVersion >= 7.00 )
{
LOADDATA(&iRowSize, pBuffer, sizeof(INT32));
LOADDATA(&iColSize, pBuffer, sizeof(INT32));
}
// Actual world size of the map we loaded!
INT32 iWorldSize = iRowSize * iColSize;
gMapTrn.ResizeTrnCfg(WORLD_ROWS, WORLD_COLS, iRowSize, iColSize);
// Initialize world data
if ( gpWorldLevelDataAutoResolve != NULL )
MemFree(gpWorldLevelDataAutoResolve);
gpWorldLevelDataAutoResolve = (MAP_ELEMENT*)MemAlloc(sizeof(MAP_ELEMENT) * WORLD_MAX);
memset(gpWorldLevelDataAutoResolve, 0, sizeof(MAP_ELEMENT) * WORLD_MAX);
bCounts = (UINT8(*)[8])MemAlloc(WORLD_MAX * 8);
memset(bCounts, 0, sizeof(UINT8) * WORLD_MAX * 8);
// Read FLAGS FOR WORLD
LOADDATA(&uiFlags, pBuffer, sizeof(INT32));
SKIPDATA(pBuffer, pBuffer, sizeof(INT32)); // TilesetID
// Load soldier size
SKIPDATA(pBuffer, pBuffer, sizeof(INT32));
// NOTE! We need this!
// Read height values
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
LOADDATA(&gpWorldLevelDataAutoResolve[cnt].sHeight, pBuffer, sizeof(INT16));
}
// Read layer counts
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
// Read combination of land/world flags
LOADDATA(&ubCombine, pBuffer, sizeof(UINT8));
// Split
bCounts[cnt][0] = (UINT8)(ubCombine & 0x0F);
gpWorldLevelDataAutoResolve[cnt].uiFlags |= (UINT8)((ubCombine & 0xF0) >> 4);
// Read #objects, structs
LOADDATA(&ubCombine, pBuffer, sizeof(UINT8));
// Split
bCounts[cnt][1] = (UINT8)(ubCombine & 0x0F);
bCounts[cnt][2] = (UINT8)((ubCombine & 0xF0) >> 4);
// Read shadows, roof
LOADDATA(&ubCombine, pBuffer, sizeof(UINT8));
// Split
bCounts[cnt][3] = (UINT8)(ubCombine & 0x0F);
bCounts[cnt][4] = (UINT8)((ubCombine & 0xF0) >> 4);
// Read OnRoof, nothing
LOADDATA(&ubCombine, pBuffer, sizeof(UINT8));
// Split
bCounts[cnt][5] = (UINT8)(ubCombine & 0x0F);
}
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
for ( cnt2 = 0; cnt2 < bCounts[cnt][0]; cnt2++ )
{
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
}
}
// New load require UINT16 for the type subindex due to the fact that ROADPIECES contain over 300 type subindices.
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
// Set objects
for ( cnt2 = 0; cnt2 < bCounts[cnt][1]; cnt2++ )
{
LOADDATA(&ubType, pBuffer, sizeof(UINT8));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT16));
if ( ubType >= FIRSTPOINTERS )
continue;
}
}
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
// Set structs
for ( cnt2 = 0; cnt2 < bCounts[cnt][2]; cnt2++ )
{
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
}
}
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
for ( cnt2 = 0; cnt2 < bCounts[cnt][3]; cnt2++ )
{
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
}
}
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
for ( cnt2 = 0; cnt2 < bCounts[cnt][4]; cnt2++ )
{
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
}
}
for ( i = 0; i < iWorldSize; i++ )
{
gMapTrn.GetTrnCnt(cnt = i);
for ( cnt2 = 0; cnt2 < bCounts[cnt][5]; cnt2++ )
{
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
}
}
// For old Russian Maps which have version 6.0
if ( dMajorMapVersion == 6.00 && ubMinorMapVersion < 27 )
{
UINT32 uiNums[37];
LOADDATA(uiNums, pBuffer, sizeof(INT32) * 37);
dMajorMapVersion = 5.00;
}
// For new maps
else if ( dMajorMapVersion == 6.00 && ubMinorMapVersion < 27 && MAJOR_MAP_VERSION == 6.00 )
{
UINT32 uiNums[37];
LOADDATA(uiNums, pBuffer, sizeof(UINT32) * 37);
}
//now the data is discarded and when saved, as 6.27, you won't have this problem!
for ( i = 0; i < iWorldSize; i++ ) {
// Read room information, 2 byte for new files
if ( ubMinorMapVersion < 29 ) {
SKIPDATA(pBuffer, pBuffer, sizeof(INT8));
}
else {
SKIPDATA(pBuffer, pBuffer, sizeof(UINT16));
}
}
// Load out item information
if ( uiFlags & MAP_WORLDITEMS_SAVED )
{
WORLDITEM dummyItem;
UINT32 uiNumWorldItems;
//Read the number of items that were saved in the map.
LOADDATA(&uiNumWorldItems, pBuffer, 4);
if ( gTacticalStatus.uiFlags & LOADING_SAVED_GAME && !gfEditMode )
{ //The sector has already been visited. The items are saved in a different format that will be
//loaded later on. So, all we need to do is skip the data entirely.
if ( dMajorMapVersion >= 6.0 && ubMinorMapVersion > 26 ) {
for ( unsigned int x = 0; x < uiNumWorldItems; ++x )
{
//ADB WORLDITEM's size on disk is unknown
dummyItem.Load(&pBuffer, dMajorMapVersion, ubMinorMapVersion);
}
}
else
{
pBuffer += sizeof(OLD_WORLDITEM_101) * uiNumWorldItems;
}
}
else for ( i = 0; i < uiNumWorldItems; i++ )
{
dummyItem.Load(&pBuffer, dMajorMapVersion, ubMinorMapVersion);
}
}
if ( uiFlags & MAP_AMBIENTLIGHTLEVEL_SAVED )
{
//Ambient light levels are only saved in underground levels
SKIPDATA(pBuffer, pBuffer, sizeof(BOOLEAN));
SKIPDATA(pBuffer, pBuffer, sizeof(BOOLEAN));
SKIPDATA(pBuffer, pBuffer, sizeof(UINT8));
}
if ( uiFlags & MAP_WORLDLIGHTS_SAVED )
{
SGPPaletteEntry LColors[3];
UINT8 ubNumColors;
UINT16 usNumLights;
CHAR8 str[30];
UINT8 ubStrLen;
LIGHT_SPRITE TmpLight;
// read in the light colors!
LOADDATA(&ubNumColors, pBuffer, 1);
LOADDATA(LColors, pBuffer, sizeof(SGPPaletteEntry) * ubNumColors);
LOADDATA(&usNumLights, pBuffer, 2);
for ( cnt = 0; cnt < usNumLights; cnt++ )
{
LOADDATA(&TmpLight, pBuffer, sizeof(LIGHT_SPRITE));
LOADDATA(&ubStrLen, pBuffer, 1);
if ( ubStrLen )
{
LOADDATA(str, pBuffer, ubStrLen);
}
}
}
gMapInformationAutoResolve.Load(&pBuffer, dMajorMapVersion);
// NOTE! We need these!
// Translation routine for map grid numbers
gMapTrn.GetTrnCnt(gMapInformationAutoResolve.sCenterGridNo);
gMapTrn.GetTrnCnt(gMapInformationAutoResolve.sIsolatedGridNo);
gMapTrn.GetTrnCnt(gMapInformationAutoResolve.sNorthGridNo);
gMapTrn.GetTrnCnt(gMapInformationAutoResolve.sEastGridNo);
gMapTrn.GetTrnCnt(gMapInformationAutoResolve.sWestGridNo);
gMapTrn.GetTrnCnt(gMapInformationAutoResolve.sSouthGridNo);
// Remove this rather large chunk of memory from the system now!
MemFree(pBufferHead);
MemFree(bCounts);
return(TRUE);
}
//****************************************************************************************
//
// Deletes everything then re-creates the world with simple ground tiles
@@ -4599,10 +4306,12 @@ void SetWorldSize(INT32 nWorldRows, INT32 nWorldCols)
gubGridNoMarkers = (UINT8*)MemAlloc(WORLD_MAX);
memset(gubGridNoMarkers, 0, sizeof(UINT8)*WORLD_MAX);
if(gsCoverValue)
MemFree(gsCoverValue);
gsCoverValue = (INT16*)MemAlloc(sizeof(INT16)*WORLD_MAX);
memset(gsCoverValue, 0x7F, sizeof(INT16)*WORLD_MAX);
if (DEBUG_CHEAT_LEVEL())
{
MemFree(gRenderDebugInfoValues);
gRenderDebugInfoValues = (INT32*)MemAlloc(sizeof(INT32) * WORLD_MAX);
ResetDebugInfoValues();
}
// Init building structures and variables
if(gubBuildingInfo)
-2
View File
@@ -281,7 +281,6 @@ typedef struct
// World Data
extern MAP_ELEMENT *gpWorldLevelData;
extern MAP_ELEMENT* gpWorldLevelDataAutoResolve;
extern UINT8 (*gubWorldMovementCosts)[MAXDIR][2];//dnl ch43 260909
//dnl ch44 290909 Translation routine
@@ -340,7 +339,6 @@ BOOLEAN NewWorld( INT32 nMapRows, INT32 nMapCols );
BOOLEAN SaveWorld(const STR8 puiFilename, FLOAT dMajorMapVersion=MAJOR_MAP_VERSION, UINT8 ubMinorMapVersion=MINOR_MAP_VERSION);//dnl ch33 150909
BOOLEAN LoadWorld(const STR8 puiFilename, FLOAT* pMajorMapVersion=NULL, UINT8* pMinorMapVersion=NULL);//dnl ch44 290909
BOOLEAN LoadWorldInfoForAutoResolve(const STR8 puiFilename, INT32& iRowSize, INT32& iColSize);
void CompileWorldMovementCosts(void);//dnl ch56 151009
void RecompileLocalMovementCosts( INT32 sCentreGridNo );
+47 -58
View File
@@ -199,10 +199,7 @@ WRAPPED_STRING *LineWrap(INT32 iFont, UINT16 usLineWidthPixels, UINT16 *pusLineW
usCurrentWidthPixels = usCurrentWidthPixels + WFStringPixLength( OneChar, iFont);
//If we are at the end of the string
//Juqi: Add NEWLINE_CHAR detect
// NEWLINE_CHAR "±" is only used to handle Chinese ITMES.XML file.
if (TempString[ usCurIndex ] == 0 && !fNewLine)
if(TempString[ usCurIndex ] == 0)
{
//get to next WrappedString structure
pWrappedString = &FirstWrappedString;
@@ -227,13 +224,10 @@ WRAPPED_STRING *LineWrap(INT32 iFont, UINT16 usLineWidthPixels, UINT16 *pusLineW
}
//Juqi: Allow manual line breaks to be added in XML,
// rather than relying solely on width for automatic line breaks.
if((usCurrentWidthPixels > usLineWidthPixels || fNewLine)
&&
((g_lang != i18n::Lang::zh)
||
(g_lang == i18n::Lang::zh
if( (g_lang != i18n::Lang::zh && usCurrentWidthPixels > usLineWidthPixels)
||
(g_lang == i18n::Lang::zh && usCurrentWidthPixels > usLineWidthPixels
&& TempString[usCurIndex] != L''
&& TempString[usCurIndex] != L''
&& TempString[usCurIndex] != L''
@@ -245,9 +239,8 @@ WRAPPED_STRING *LineWrap(INT32 iFont, UINT16 usLineWidthPixels, UINT16 *pusLineW
&& TempString[usCurIndex] != L''
&& TempString[usCurIndex] != L')'
&& TempString[usCurIndex] != L''
))
)
)//||(DestString[ usDestIndex ]==NEWLINE_CHAR )||(fNewLine))
)
{
//if an error has occured, and the string is too long
@@ -258,55 +251,51 @@ WRAPPED_STRING *LineWrap(INT32 iFont, UINT16 usLineWidthPixels, UINT16 *pusLineW
// Fix really long unbroken strings, the above code dosent work for it
usLastMaxWidthIndex = usDestIndex;
//Juqi: if NEWLINE_CHAR, don't go back
if (!fNewLine)
{
//Go back to begining of word
while(
(g_lang != i18n::Lang::zh && DestString[ usDestIndex ] != L' ' && usCurIndex > 0 && usDestIndex > 0)
||
(g_lang == i18n::Lang::zh && DestString[ usDestIndex ] != L' ' && usCurIndex > 0 && usDestIndex > 0 && DestString[usDestIndex] < 255))
{
OneChar[0] = DestString[ usDestIndex ];
//Go back to begining of word
while(
(g_lang != i18n::Lang::zh && DestString[ usDestIndex ] != L' ' && usCurIndex > 0 && usDestIndex > 0)
||
(g_lang == i18n::Lang::zh && DestString[ usDestIndex ] != L' ' && usCurIndex > 0 && usDestIndex > 0 && DestString[usDestIndex] < 255))
{
OneChar[0] = DestString[ usDestIndex ];
usCurrentWidthPixels = usCurrentWidthPixels - WFStringPixLength( OneChar, iFont);
usCurrentWidthPixels = usCurrentWidthPixels - WFStringPixLength( OneChar, iFont);
usCurIndex--;
usDestIndex--;
}
if (g_lang == i18n::Lang::zh && DestString[usDestIndex] > 255)
{
if (DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L','
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L'?'
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L')'
|| DestString[usDestIndex] == L'')
{usDestIndex++;}
else
{usCurIndex--;}
}
usCurIndex--;
usDestIndex--;
}
if (g_lang == i18n::Lang::zh && DestString[usDestIndex] > 255)
{
if (DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L','
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L'?'
|| DestString[usDestIndex] == L''
|| DestString[usDestIndex] == L')'
|| DestString[usDestIndex] == L'')
{usDestIndex++;}
else
{usCurIndex--;}
}
usEndIndex = usDestIndex;
usEndIndex = usDestIndex;
// OJW - 20090427
// Fix really long unbroken strings
if( usEndIndex <= 0 )
{
usEndIndex = usLastMaxWidthIndex;
usDestIndex = usLastMaxWidthIndex;
usCurIndex += usLastMaxWidthIndex - 1;
}
// OJW - 20090427
// Fix really long unbroken strings
if( usEndIndex <= 0 )
{
usEndIndex = usLastMaxWidthIndex;
usDestIndex = usLastMaxWidthIndex;
usCurIndex += usLastMaxWidthIndex - 1;
}
// put next line into temp buffer
DestString[usEndIndex] = 0;
} //Juqi: endif
// put next line into temp buffer
DestString[usEndIndex] = 0;
//get to next WrappedString structure
pWrappedString = &FirstWrappedString;
+145 -148
View File
@@ -2361,11 +2361,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"没有能够训练的民兵。", //L"No militia that can be drilled present.",
L"%s 已经完全的探索了 %s。", //L"%s has fully explored %s."
// The first %s is a merc name and the second %s is an item name
L"%s 排除了 %s 的故障。", //L"%s unjammed %s.",
L"%s 未能排除 %s 的故障。", //L"%s failed to unjam %s.",
L"%s 的行动点数不足以排除 %s 的故障。", //L"%s does not have enough APs to unjam %s.",
};
// the country and its noun in the game
@@ -6646,6 +6641,7 @@ STR16 zOptionsToggleText[] =
L"显示已知敌人位置", //L"Show enemy location", show locator on last known enemy location
L"准心开始时为最大", // L"Start at maximum aim",
L"替换新的寻路方式", // L"Alternative pathfinding",
L"Use old Tactical AI", // L"Use old Tactical AI",
L"--作弊模式选项--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"强制 Bobby Ray 送货", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6767,6 +6763,7 @@ STR16 zOptionsScreenHelpText[] =
L"打开时,会显示已知敌人最后移动的位置。", //L"When ON, shows last known enemy location.",
L"打开时,默认瞄准值为最大,而不是无。", //L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"打开时,使用A*寻路算法,而不是原始算法。", //L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"强制 Bobby Ray 出货",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
@@ -7556,7 +7553,7 @@ STR16 gzLateLocalizedString[] =
L"%s用完了医药箱里的药品!", //"%s ran out of medical kits!",
L"%s没有所需技能来医疗他人!", //"%s lacks the necessary skill to doctor anyone!",
L"%s用完工具箱里的工具!", //"%s ran out of tool kits!",
L"%s没有所需技能或属性来修理物品!", //"%s lacks the necessary skill to repair anything!",
L"%s没有所需技能来修理物品!", //"%s lacks the necessary skill to repair anything!",
//44-45
L"修理时间", //L"Repair Time",
@@ -9390,7 +9387,7 @@ STR16 szBackgroundText_Value[]=
L" %s%d% 对人员治疗疾病的效率 \n", //L" %s%d%% effectiveness at treating population against diseases\n",
L" 能够发现%d格之内的脚印 \n", //L"Can spot tracks up to %d tiles away\n",
L" %s%d% 被伏击时的初始距离 \n", //L" %s%d%% initial distance to enemy in ambush\n",
L" %s%d% 闪避蛇类攻击 \n", //L" %s%d%% chance to evade snake attacks\n",
L" %s%d% 几率回避攻击 \n", //L" %s%d%% chance to evade snake attacks\n",
L" 对某些其他背景的厌恶 \n", //L" dislikes some other backgrounds\n",
L" 吸烟者", //L"Smoker",
@@ -9487,8 +9484,8 @@ STR16 szNationalityText[]=
L"古巴人", // 5
L"丹麦人",
L"法国人",
L"罗斯",
L"特拉科纳", // (UB takes place in Tracona)
L"",
L"Tracona", // (UB takes place in Tracona)
L"瑞士人", // 10
L"牙买加人",
L"波兰人",
@@ -9497,103 +9494,103 @@ STR16 szNationalityText[]=
L"南非人", // 15
L"匈牙利人",
L"苏格兰人",
L"阿鲁科",
L"Arulco",
L"德国人",
L"非洲人", // 20
L"意大利人",
L"荷兰人",
L"罗马亚人",
L"梅塔维拉",
L"罗马亚人",
L"Metavira",
// newly added from here on
L"阿富汗人", // 25
L"阿尔巴尼亚人",
L"阿根廷人",
L"亚美尼亚人",
L"阿塞拜疆人",
L"孟加拉国人", // 30
L"Afghan", // 25
L"Albanian",
L"Argentinian",
L"Armenian",
L"Azerbaijani",
L"Bangladeshi ", // 30
L"白俄罗斯人",
L"比利时人",
L"贝宁人",
L"玻利维亚人",
L"波斯尼亚人", // 35
L"Beninese",
L"Bolivian",
L"Bosnian", // 35
L"巴西人",
L"保加利亚人",
L"柬埔寨人",
L"乍得人",
L"智利人", // 40
L"哥伦比亚人",
L"刚果人",
L"克罗地亚人",
L"厄瓜多尔人",
L"埃及人", // 45
L"英格兰人",
L"厄立特里亚人",
L"Cambodian",
L"Chadian",
L"Chilean", // 40
L"Columbian",
L"Congolese",
L"Croatian",
L"Ecuadorian",
L"Egyptian", // 45
L"English",
L"Eritrean",
L"爱沙尼亚人",
L"埃塞俄比亚人",
L"菲律宾人", // 50
L"Ethiopian",
L"Filipino", // 50
L"芬兰人",
L"格鲁吉亚人",
L"Georgian",
L"希腊人",
L"危地马拉人",
L"海地人", // 55
L"洪都拉斯人",
L"Guatemalan",
L"Haitian", // 55
L"Honduran",
L"印度人",
L"印度尼西亚人",
L"Indonesian",
L"伊朗人",
L"伊拉克人", // 60
L"冰岛人",
L"Islandic",
L"以色列人",
L"日本人",
L"约旦人",
L"哈萨克斯坦人", // 65
L"韩国人和朝鲜人",
L"吉尔吉斯斯坦人",
L"老挝人",
L"拉脱维亚人",
L"黎巴嫩人", // 70
L"立陶宛人",
L"利比亚人",
L"马其顿人",
L"马来西亚人",
L"Jordanian",
L"Kazakhstani", // 65
L"Korean",
L"Kyrgyzstani",
L"Laotian",
L"Latvian",
L"Lebanese", // 70
L"Lithuanian",
L"Lybian",
L"Macedonian",
L"Malaysian",
L"墨西哥人", // 75
L"蒙古人",
L"摩洛哥人",
L"莫桑比克人",
L"缅甸人",
L"纳米比亚人", // 80
L"尼加拉瓜人",
L"Mongolian",
L"Moroccan",
L"Mozambican",
L"Myanma",
L"Namibian", // 80
L"Nicaraguan",
L"尼日利亚人",
L"尼日尔人",
L"Nigerien",
L"挪威人",
L"巴基斯坦人", // 85
L"巴拿马人",
L"Pakistani", // 85
L"Panamanian",
L"葡萄牙人",
L"卢旺达人",
L"萨尔瓦多人",
L"沙特人", // 90
L"Rwandanese",
L"Salvadoran",
L"Saudi", // 90
L"塞尔维亚人",
L"斯洛伐克人",
L"斯洛文尼亚人",
L"索马里人",
L"Slovakian",
L"Slovenian",
L"Somali",
L"西班牙人", // 95
L"苏丹人",
L"Sudanese",
L"瑞典人",
L"叙利亚人",
L"泰国人",
L"多哥人", // 100
L"突尼斯人",
L"Thai",
L"Togolese", // 100
L"Tunisian",
L"土耳其人",
L"乌干达人",
L"乌克兰人",
L"乌拉圭人", // 105
L"乌兹别克斯坦人",
L"Ugandan",
L"Ukrainian",
L"Uruguayan", // 105
L"Uzbekistani",
L"委内瑞拉人",
L"越南人",
L"威尔士人",
L"也门人", // 110
L"扎蒙达人", // Zamunda
L"津巴布韦人",
L"Vietnamese",
L"Welsh",
L"Yemeni", // 110
L"Zamundan", // Zamunda
L"Zimbabwean",
};
STR16 szNationalityTextAdjective[] =
@@ -9606,8 +9603,8 @@ STR16 szNationalityTextAdjective[] =
L"古巴人", // 5
L"丹麦人",
L"法国人",
L"罗斯",
L"特拉科纳" // UB takes place in Tracona
L"",
L"traconia" // UB takes place in Tracona
L"瑞士人", // 10
L"牙买加人",
L"波兰人",
@@ -9616,103 +9613,103 @@ STR16 szNationalityTextAdjective[] =
L"南非人", // 15
L"匈牙利人",
L"苏格兰人",
L"阿鲁科",
L"Arulco",
L"德国人",
L"非洲人", // 20
L"意大利人",
L"荷兰人",
L"罗马亚人",
L"梅塔维拉",
L"罗马亚人",
L"Metavira",
// newly added from here on
L"阿富汗人", // 25
L"阿尔巴尼亚人",
L"阿根廷人",
L"亚美尼亚人",
L"阿塞拜疆人",
L"孟加拉国人", // 30
L"afghans", // 25
L"albanians",
L"argentinians",
L"armenians",
L"azerbaijani",
L"bangladeshi", // 30
L"白俄罗斯人",
L"比利时人",
L"贝宁人",
L"玻利维亚人",
L"波斯尼亚人", // 35
L"beninese",
L"bolivians",
L"bosnians", // 35
L"巴西人",
L"保加利亚人",
L"柬埔寨人",
L"乍得人",
L"智利人", // 40
L"哥伦比亚人",
L"刚果人",
L"克罗地亚人",
L"厄瓜多尔人",
L"埃及人", // 45
L"英格兰人",
L"厄立特里亚人",
L"cambodians",
L"chadians",
L"chileans", // 40
L"columbians",
L"congolese",
L"croatians",
L"ecuadorians",
L"egyptians", // 45
L"englishmen",
L"eritreans",
L"爱沙尼亚人",
L"埃塞俄比亚人",
L"菲律宾人", // 50
L"ethiopians",
L"filipinos", // 50
L"芬兰人",
L"格鲁吉亚人",
L"georgians",
L"希腊人",
L"危地马拉人",
L"海地人", // 55
L"洪都拉斯人",
L"guatemalans",
L"haitians", // 55
L"hondurans",
L"印度人",
L"印度尼西亚人",
L"indonesians",
L"伊朗人",
L"伊拉克人", // 60
L"冰岛人",
L"islandics",
L"以色列人",
L"日本人",
L"约旦人",
L"哈萨克斯坦人", // 65
L"韩国人和朝鲜人",
L"吉尔吉斯斯坦人",
L"老挝人",
L"拉脱维亚人",
L"黎巴嫩人", // 70
L"立陶宛人",
L"利比亚人",
L"马其顿人",
L"马来西亚人",
L"jordanians",
L"kazakhstani", // 65
L"koreans",
L"kyrgyzstani",
L"laotians",
L"latvians",
L"lebanese", // 70
L"lithuanians",
L"lybians",
L"macedonians",
L"malaysians",
L"墨西哥人", // 75
L"蒙古人",
L"摩洛哥人",
L"莫桑比克人",
L"缅甸人",
L"纳米比亚人", // 80
L"尼加拉瓜人",
L"mongolians",
L"moroccans",
L"mozambicans",
L"myanmarians",
L"namibians", // 80
L"nicaraguans",
L"尼日利亚人",
L"尼日尔人",
L"nigeriens",
L"挪威人",
L"巴基斯坦人", // 85
L"巴拿马人",
L"panamanians",
L"葡萄牙人",
L"卢旺达人",
L"萨尔瓦多人",
L"沙特人", // 90
L"rwandanese",
L"salvadorans",
L"saudis", // 90
L"塞尔维亚人",
L"斯洛伐克人",
L"斯洛文尼亚人",
L"索马里人",
L"slovakians",
L"slovenians",
L"somali",
L"西班牙人", // 95
L"苏丹人",
L"sudanese",
L"瑞典人",
L"叙利亚人",
L"泰国人",
L"多哥人", // 100
L"突尼斯人",
L"thais",
L"togolese", // 100
L"tunisians",
L"土耳其人",
L"乌干达人",
L"乌克兰人",
L"乌拉圭人", // 105
L"乌兹别克斯坦人",
L"ugandans",
L"ukrainians",
L"uruguayans", // 105
L"uzbekistani",
L"委内瑞拉人",
L"越南人",
L"威尔士人",
L"也门人", // 110
L"扎蒙达人", // Zamunda
L"津巴布韦人",
L"vietnamese",
L"welshs",
L"yemenites", // 110
L"zamundans", // Zamunda
L"zimbabweans",
};
// special text used if we do not hate any nation (value of -1)
+2 -5
View File
@@ -2360,11 +2360,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s has fully explored %s.", // TODO.Translate
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game
@@ -6648,6 +6643,7 @@ STR16 zOptionsToggleText[] =
L"Show enemy location", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6769,6 +6765,7 @@ STR16 zOptionsScreenHelpText[] =
L"When ON, shows last known enemy location.", //TODO.Translate
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Force all pending Bobby Ray shipments",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
+2 -5
View File
@@ -2361,11 +2361,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s has fully explored %s.",
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game
@@ -6646,6 +6641,7 @@ STR16 zOptionsToggleText[] =
L"Show enemy location", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Force Bobby Ray Shipments", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6767,6 +6763,7 @@ STR16 zOptionsScreenHelpText[] =
L"When ON, shows last known enemy location.",
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Force all pending Bobby Ray shipments",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
+2 -5
View File
@@ -2369,11 +2369,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s has fully explored %s.", // TODO.Translate
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game // TODO.Translate
@@ -6653,6 +6648,7 @@ STR16 zOptionsToggleText[] =
L"Show enemy location", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Options mode triche--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Forcer envois Bobby Ray", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6773,6 +6769,7 @@ STR16 zOptionsScreenHelpText[] =
L"When ON, shows last known enemy location.", //TODO.Translate
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Forcer tous les envois en attente de Bobby Ray",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
+2 -5
View File
@@ -2403,11 +2403,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s hat %s vollständig erkundet.",
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game
@@ -6520,6 +6515,7 @@ STR16 zOptionsToggleText[] =
L"Show enemy location", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Erzwinge BR Lieferung", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6641,6 +6637,7 @@ STR16 zOptionsScreenHelpText[] =
L"When ON, shows last known enemy location.", //TODO.Translate
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Force all pending Bobby Ray shipments",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
+2 -5
View File
@@ -2355,11 +2355,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s has fully explored %s.", // TODO.Translate
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game
@@ -6631,6 +6626,7 @@ STR16 zOptionsToggleText[] =
L"Show enemy location", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6752,6 +6748,7 @@ STR16 zOptionsScreenHelpText[] =
L"When ON, shows last known enemy location.", //TODO.Translate
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Force all pending Bobby Ray shipments",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
+2 -2
View File
@@ -396,8 +396,8 @@ STR16 gzIMPMajorTraitsHelpTextsSurvival[] =
L"疾病抗性 %s%d%%\n",//L" %s%d%% disease resistance\n",
L"食物消耗 %s%d%%\n",//L" %s%d%% food consumption\n",
L"耗 %s%d%%\n",//L" %s%d%% water consumption\n",
L"闪避蛇类 +%d%%\n", //L"+%d%% snake evasion\n",
L"耗 %s%d%%\n",//L" %s%d%% water consumption\n",
L"回避几率 +%d%%\n", //L"+%d%% snake evasion\n",
L"迷彩涂装效果 +%d%%\n",// L"+%d%s camouflage effectiveness\n",
};
+2 -5
View File
@@ -2367,11 +2367,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s has fully explored %s.", // TODO.Translate
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game
@@ -6650,6 +6645,7 @@ STR16 zOptionsToggleText[] =
L"Show enemy location", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6771,6 +6767,7 @@ STR16 zOptionsScreenHelpText[] =
L"When ON, shows last known enemy location.", //TODO.Translate
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Wymuś wszystkie oczekiwane dostawy od Bobby Ray's.",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
+2 -5
View File
@@ -2361,11 +2361,6 @@ CHAR16 Message[][STRING_LENGTH] =
L"No militia that can be drilled present.",
L"%s has fully explored %s.", // TODO.Translate
// The first %s is a merc name and the second %s is an item name
L"%s unjammed %s.",
L"%s failed to unjam %s.",
L"%s does not have enough APs to unjam %s."
};
// the country and its noun in the game
@@ -6642,6 +6637,7 @@ STR16 zOptionsToggleText[] =
L"Показывать расположение", // show locator on last known enemy location
L"Start at maximum aim",
L"Alternative pathfinding",
L"Use old Tactical AI",
L"--Читерские настройки--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER,
L"Ускорить доставку Бобби Рэя", // force all pending Bobby Ray shipments
L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END
@@ -6763,6 +6759,7 @@ STR16 zOptionsScreenHelpText[] =
L"Если включено, показывает известное расположение противника\n(нажмите |S|h|i|f|t, чтобы показать источник шума).",
L"When ON, aiming at enemy will start at maximum aiming instead of default no aim",
L"When ON, Use A* pathfinding algorithm, instead of original",
L"When ON, Uses the old tactical AI",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER",
L"Выберите этот пункт,\nчтобы груз Бобби Рэя прибыл немедленно.",
L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END",
-4
View File
@@ -612,10 +612,6 @@ enum
STR_ASSIGNMENT_EXPLORATION_DONE,
STR_UNJAMMED,
STR_FAILED_UNJAM,
STR_NO_AP_NO_UNJAM,
TEXT_NUM_STR_MESSAGE,
};
+5 -19
View File
@@ -836,11 +836,8 @@ void Load32bppPNGImage(HIMAGE hImage, png::png_bytepp rows, png::png_infop info)
hImage->p32BPPData = (UINT32*)MemAlloc(SIZE);
if(!hImage->p32BPPData)
{
CHAR8 errorText[512];
sprintf(errorText, "Bad allocation loading image. Filename: %s", hImage->ImageFile);
MemFree(hImage->pETRLEObject);
SGP_THROW(errorText);
SGP_THROW(L"bad alloc");
}
memset(hImage->p32BPPData, 0, SIZE);
@@ -881,11 +878,8 @@ void Load24bppPNGImage(HIMAGE hImage, png::png_bytepp rows, png::png_infop info)
hImage->p16BPPData = (UINT16*)MemAlloc(SIZE);
if(!hImage->p16BPPData)
{
CHAR8 errorText[512];
sprintf(errorText, "Bad allocation loading image. Filename: %s", hImage->ImageFile);
MemFree(hImage->pETRLEObject);
SGP_THROW(errorText);
SGP_THROW(L"bad alloc");
}
memset(hImage->p16BPPData, 0, SIZE);
@@ -905,15 +899,11 @@ void Load24bppPNGImage(HIMAGE hImage, png::png_bytepp rows, png::png_infop info)
void LoadPalettedPNGImage(HIMAGE hImage, png::png_bytepp rows, png::png_infop info)
{
CHAR8 errorText[512];
UINT32 SIZE = info->height * info->width * sizeof(UINT8);
UINT8 *data = (UINT8*)MemAlloc(SIZE);
if(!data)
{
sprintf(errorText, "Bad allocation loading image. Filename: %s", hImage->ImageFile);
MemFree(hImage->pETRLEObject);
SGP_THROW(errorText);
SGP_THROW(L"bad alloc");
}
memset(data,0,SIZE);
for(unsigned int i = 0; i < info->height; ++i)
@@ -925,10 +915,8 @@ void LoadPalettedPNGImage(HIMAGE hImage, png::png_bytepp rows, png::png_infop in
hImage->pETRLEObject = (ETRLEObject*)MemAlloc(hImage->usNumberOfObjects * sizeof(ETRLEObject));
if(!hImage->pETRLEObject)
{
sprintf(errorText, "Bad allocation for ETRLEObject. Filename: %s", hImage->ImageFile);
MemFree(data);
SGP_THROW(errorText);
SGP_THROW(L"bad alloc");
}
memset(hImage->pETRLEObject,0, hImage->usNumberOfObjects * sizeof(ETRLEObject));
@@ -943,11 +931,9 @@ void LoadPalettedPNGImage(HIMAGE hImage, png::png_bytepp rows, png::png_infop in
hImage->p8BPPData = (UINT8*)MemAlloc( RESIZE );
if(!hImage->p8BPPData)
{
sprintf(errorText, "Bad allocation for image pixel data. Filename: %s", hImage->ImageFile);
MemFree(hImage->pETRLEObject);
MemFree(data);
SGP_THROW(errorText);
SGP_THROW(L"bad alloc");
}
UINT8 *compressed = hImage->p8BPPData;
memset(hImage->p8BPPData,0,RESIZE);