From 335747c3ffb06cd52f745b9e557089c91a2b1f93 Mon Sep 17 00:00:00 2001 From: Wanne Date: Wed, 1 Apr 2009 12:20:30 +0000 Subject: [PATCH] Added Headrock's HAM 2.8 git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@2650 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- GameSettings.cpp | 138 +++++ GameSettings.h | 136 ++++- SaveLoadGame.cpp | 7 + Strategic/Assignments.cpp | 443 +++++++++++++- Strategic/Assignments.h | 9 + Strategic/Auto Resolve.cpp | 4 +- Strategic/MilitiaSquads.cpp | 825 ++++++++++++++++++++++++--- Strategic/MilitiaSquads.h | 12 + Strategic/Strategic Merc Handler.cpp | 7 + Strategic/Strategic Mines.cpp | 12 +- Strategic/Strategic Town Loyalty.cpp | 7 + Strategic/mapscreen.cpp | 262 ++++++++- Strategic/mapscreen.h | 3 + Tactical/Bullets.h | 4 + Tactical/Campaign.cpp | 128 ++++- Tactical/Campaign.h | 10 +- Tactical/DisplayCover.cpp | 9 +- Tactical/Handle UI.cpp | 8 +- Tactical/Handle UI.h | 6 +- Tactical/Inventory Choosing.cpp | 3 +- Tactical/Items.cpp | 90 +++ Tactical/LOS.cpp | 48 +- Tactical/Overhead.cpp | 173 +++++- Tactical/Overhead.h | 2 + Tactical/Points.cpp | 38 +- Tactical/Rotting Corpses.cpp | 14 +- Tactical/Soldier Control.cpp | 14 +- Tactical/Turn Based Input.cpp | 395 +++++++++---- Tactical/UI Cursors.cpp | 245 +++++++- Tactical/UI Cursors.h | 3 + Tactical/Weapons.cpp | 251 +++++++- Tactical/Weapons.h | 3 +- Tactical/bullets.cpp | 12 +- TacticalAI/AIInternals.h | 4 + TacticalAI/AIUtils.cpp | 233 ++++++++ TacticalAI/Attacks.cpp | 24 +- TacticalAI/DecideAction.cpp | 42 +- Utils/Cursors.cpp | 110 ++-- Utils/Text.h | 2 + Utils/_ChineseText.cpp | 12 +- Utils/_DutchText.cpp | 12 +- Utils/_EnglishText.cpp | 13 +- Utils/_FrenchText.cpp | 12 +- Utils/_GermanText.cpp | 12 +- Utils/_ItalianText.cpp | 12 +- Utils/_PolishText.cpp | 12 +- Utils/_RussianText.cpp | 12 +- Utils/_TaiwaneseText.cpp | 12 +- 48 files changed, 3439 insertions(+), 406 deletions(-) diff --git a/GameSettings.cpp b/GameSettings.cpp index b6b86d87..d52c456a 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -812,6 +812,144 @@ void LoadGameExternalOptions() // WANNE: Always use prof.dat?? gGameExternalOptions.fAlwaysUseProfDat = iniReader.ReadBoolean("JA2 Gameplay Settings", "ALWAYS_USE_PROF_DAT", FALSE); + + + // HEADROCK HAM B1: Set % of mine income, where 100% = normal + gGameExternalOptions.iMineIncomePercentage = iniReader.ReadInteger("JA2 HAM Settings","MINE_INCOME_PERCENTAGE",0); + // HEADROCK HAM B2.4: Set a threshold at 1% - otherwise game crashes on asserting mine income. + // Can anyone have a look at circumventing the income check? I don't want to cause any damage... + gGameExternalOptions.iMineIncomePercentage = __max(gGameExternalOptions.iMineIncomePercentage, 1); + + // HEADROCK HAM B1: Set minimum and maximum CTH + gGameExternalOptions.iMaximumCTH = iniReader.ReadInteger("JA2 HAM Settings","MAXIMUM_POSSIBLE_CTH",0); + + gGameExternalOptions.iMinimumCTH = iniReader.ReadInteger("JA2 HAM Settings","MINIMUM_POSSIBLE_CTH",0); + + // HEADROCK HAM B1: Set minimum CTH at fraction between 0 and 1 ( MINCTH = 1/(100*divisor) ) Note Minimum above must be 0. + gGameExternalOptions.iMinimumCTHDivisor = iniReader.ReadInteger("JA2 HAM Settings","MINIMUM_CTH_DIVISOR",0); + + // HEADROCK HAM B1: Allow restricted militia to move through visited sectors? + gGameExternalOptions.bUnrestrictVisited = iniReader.ReadBoolean("JA2 HAM Settings","ALLOW_RESTRICTED_MILITIA_THROUGH_VISITED_SECTORS",0); + + gGameExternalOptions.bDynamicRestrictRoaming = iniReader.ReadBoolean("JA2 HAM Settings","ALLOW_DYNAMIC_RESTRICTED_ROAMING",0); + + // HEADROCK HAM B2: Reset suppression counter. 0 = never (oldskool), 1 = Every turn, 2 = Every attack. + gGameExternalOptions.iClearSuppression = iniReader.ReadInteger("JA2 HAM Settings","CLEAR_SUPPRESSION_COUNTER",0); + + // HEADROCK HAM B2.1: This controls how effective suppression is, by increasing the number of ubSuppressionPoints accumulated by combatants (percentage); + gGameExternalOptions.iSuppressionEffectiveness = iniReader.ReadInteger("JA2 HAM Settings","SUPPRESSION_EFFECTIVENESS",100); + + // HEADROCK HAM B2: MAXIMUM number of APs that can be lost to suppression in a given turn (0=unlimited) + gGameExternalOptions.iMaxSuppressionAPLossPerTurn = iniReader.ReadInteger("JA2 HAM Settings","MAX_SUPPRESSION_AP_LOSS_PER_TURN",8); + + // HEADROCK HAM B2: MAXIMUM number of APs that can be lost to suppression in a given attack (0=unlimited) + gGameExternalOptions.iMaxSuppressionAPLossPerAttack = iniReader.ReadInteger("JA2 HAM Settings","MAX_SUPPRESSION_AP_LOSS_PER_ATTACK",8); + + // HEADROCK HAM B2: Rigid limit to which a soldier's APs can drop due to suppression fire. (0=unlimited) + gGameExternalOptions.iMinAPLimitFromSuppression = iniReader.ReadInteger("JA2 HAM Settings","MIN_LIMIT_SUPPRESSION_AP_LOSS",0); + + // HEADROCK HAM B2: Suppression Shock ON/OFF + gGameExternalOptions.fSuppressionShock = iniReader.ReadBoolean("JA2 HAM Settings","SUPPRESSION_SHOCK",FALSE); + + // HEADROCK HAM B2: Suppression Shock effectiveness (percentage, 100 = "normal", 0 = deactivated. Range 0-65535) + gGameExternalOptions.iSuppressionShockEffectiveness = iniReader.ReadInteger("JA2 HAM Settings","SUPPRESSION_SHOCK_EFFECTIVENESS",100); + + // HEADROCK HAM B2.1: CTH penalty given by a "Cowering" target to any enemy shooter. + gGameExternalOptions.iAimPenaltyPerTargetShock = iniReader.ReadInteger("JA2 HAM Settings","AIM_PENALTY_PER_TARGET_SHOCK",0); + + // HEADROCK HAM B2.3: A "cowering" soldier is twice as susceptible to suppression. + gGameExternalOptions.iCowerEffectOnSuppression = iniReader.ReadInteger("JA2 HAM Settings","COWER_EFFECT_ON_SUPPRESSION",0); + + // HEADROCK HAM B2.4: How many APs must be lost due to suppression for every 1 point of morale lost? + gGameExternalOptions.iAPLostPerMoraleDrop = iniReader.ReadInteger("JA2 HAM Settings","AP_LOST_PER_MORALE_DROP",2); + + // HEADROCK HAM B2.5: Turn on Realistic Tracers. 0 = off (regular tracers). 1 = Fully realistic tracers. 2 = Tracer Bump + 1.13 Autofire Penalty Reduction + gGameExternalOptions.iRealisticTracers = iniReader.ReadInteger("JA2 HAM Settings","REALISTIC_TRACERS",0); + + // HEADROCK HAM B2.5: Realistic tracers - one of every X bullets in a tracer magazines will be a tracer bullet. 0 = off (JA2 normal) + gGameExternalOptions.iNumBulletsPerTracer = __max(iniReader.ReadInteger("JA2 HAM Settings","NUM_BULLETS_PER_TRACER",1),1); + + // HEADROCK HAM B2.5: Realistic tracers - CTH increased by this amount whenever a tracer is fired. 0 = off. + gGameExternalOptions.iCTHBumpPerTracer = iniReader.ReadInteger("JA2 HAM Settings","CTH_BUMP_PER_TRACER",0); + + // HEADROCK HAM B2.6: Increased aiming costs? + gGameExternalOptions.fIncreasedAimingCost = iniReader.ReadBoolean("JA2 HAM Settings","INCREASED_AIM_COST", FALSE); + + // HEADROCK HAM B2.6: Dynamically determine Max-Aiming based also on weapon, bipod, etc? + gGameExternalOptions.fDynamicAimingTime = iniReader.ReadBoolean("JA2 HAM Settings","DYNAMIC_AIM_LIMITS", FALSE); + + // HEADROCK HAM B2.6: Controls how much effect target movement has on aiming + //gGameExternalOptions.iMovementEffectOnAiming = iniReader.ReadFloat("JA2 HAM Settings","MOVEMENT_EFFECT_ON_AIMING", 1.5); + gGameExternalOptions.iMovementEffectOnAiming = (float)iniReader.ReadDouble("JA2 HAM Settings","MOVEMENT_EFFECT_ON_AIMING", 1.5, 0.0, 255.0); + + // HEADROCK HAM B2.6: Autofire Bullets/5AP modifier + gGameExternalOptions.iAutofireBulletsPer5APModifier = iniReader.ReadInteger("JA2 HAM Settings","AUTOFIRE_BULLETS_PER_5AP_MODIFIER", 0); + + // HEADROCK HAM B2.6/B1: Adjustable "luck" factor in Auto-Resolve + //gGameExternalOptions.iAutoResolveLuckFactor = (float)(__max(iniReader.ReadFloat("JA2 HAM Settings","AUTORESOLVE_LUCK_FACTOR", 2.0),1.0)); + gGameExternalOptions.iAutoResolveLuckFactor = (float)(__max(iniReader.ReadDouble("JA2 HAM Settings","AUTORESOLVE_LUCK_FACTOR", 2.0, 0.0, 255.0),1.0)); + + // HEADROCK HAM B2.6: Adjustable maximum for Suppression Shock effect. This has the added effect of reducing overall susceptibility to shock and may render some enemies unshockable. + gGameExternalOptions.iMaxSuppressionShock = iniReader.ReadInteger("JA2 HAM Settings","MAXIMUM_SUPPRESSION_SHOCK", 0); + + // HEADROCK HAM B2.6/2/1: Toggle new Burst/Auto CTH bars: 0=neither, 1=both, 2=Burst, 3=Auto + gGameExternalOptions.iNewCTHBars = iniReader.ReadInteger("JA2 HAM Settings","NEW_BURST-AUTO_CTH_BARS", 0); + + // HEADROCK HAM B2.6: Toggle whether AI checks for larger magazine when wanting to suppress at a distance + gGameExternalOptions.fIncreaseAISuppressionFire = iniReader.ReadBoolean("JA2 HAM Settings","INCREASE_AI_WILLINGNESS_TO_SUPPRESS", FALSE); + + // HEADROCK HAM B2.7: Change the speed of skill progression. (defaults set to JA2 normal) + gGameExternalOptions.ubHealthSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","HEALTH_SUBPOINTS_TO_IMPROVE", 50)); + gGameExternalOptions.ubStrengthSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","STRENGTH_SUBPOINTS_TO_IMPROVE", 50)); + gGameExternalOptions.ubDexteritySubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","DEXTERITY_SUBPOINTS_TO_IMPROVE", 50)); + gGameExternalOptions.ubAgilitySubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","AGILITY_SUBPOINTS_TO_IMPROVE", 50)); + gGameExternalOptions.ubWisdomSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","WISDOM_SUBPOINTS_TO_IMPROVE", 50)); + gGameExternalOptions.ubMarksmanshipSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","MARKSMANSHIP_SUBPOINTS_TO_IMPROVE", 25)); + gGameExternalOptions.ubMedicalSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","MEDICAL_SUBPOINTS_TO_IMPROVE", 25)); + gGameExternalOptions.ubMechanicalSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","MECHANICAL_SUBPOINTS_TO_IMPROVE", 25)); + gGameExternalOptions.ubExplosivesSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","EXPLOSIVES_SUBPOINTS_TO_IMPROVE", 25)); + gGameExternalOptions.ubLeadershipSubpointsToImprove = __max(1,iniReader.ReadInteger("JA2 HAM Settings","LEADERSHIP_SUBPOINTS_TO_IMPROVE", 25)); + gGameExternalOptions.ubLevelSubpointsToImprove = __max(1,__min(iniReader.ReadInteger("JA2 HAM Settings","LEVEL_SUBPOINTS_TO_IMPROVE", 350), 6500)); + + // HEADROCK HAM B2.7: When turned on, this will give a CTH approximation instead of an exact value, on CTH Bars and "F" key feedback. + gGameExternalOptions.fApproximateCTH = iniReader.ReadBoolean("JA2 HAM Settings","APPROXIMATE_CTH", FALSE); + + // HEADROCK HAM B2.7: Augmented Roaming Militia code - turn this to TRUE to allow militia free travel through San Mona, Tixa, Orta, Omerta, and Estoni. + gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities = iniReader.ReadBoolean("JA2 HAM Settings","ALLOW_MILITIA_MOVE_THROUGH_MINOR_CITIES", FALSE); + + // HEADROCK HAM B2.7: Smarter Roaming Militia Generator will generate Roamers in any available city perimeter square, allowing us to avoid "wasting" training sessions more easily. + gGameExternalOptions.fSmartRoamingMilitiaGenerator = iniReader.ReadBoolean("JA2 HAM Settings","SMART_ROAMING_MILITIA_GENERATOR", FALSE); + + // HEADROCK HAM B2.7: When "TRUE", Roaming Militia will be created on a 25% Veteran, 25% Regular, 50% green basis. If new militia is placed into a full group, it will upgrade greens/regulars in that group. + gGameExternalOptions.fDiverseRoamingMilitiaGroups = iniReader.ReadBoolean("JA2 HAM Settings","DIVERSE_ROAMING_MILITIA_GROUPS", FALSE); + + // HEADROCK HAM B2.8: This setting controls whether militia will try to join up into the largest groups they can, or average out the size of the groups to cover more territory. + gGameExternalOptions.ubRoamingMilitiaSpreadsOutChance = iniReader.ReadInteger("JA2 HAM Settings","ROAMING_MILITIA_SPREADOUT_CHANCE", 0); + + // HEADROCK HAM B2.8: These are new cowering penalty divisors that help us determine how effective cowering is in different stances and when the shooter is targetting different bodyparts + gGameExternalOptions.ubCoweringPenaltyDivisorProne = __max(1,iniReader.ReadInteger("JA2 HAM Settings","CTH_PENALTY_FOR_COWERING_PRONE_TARGET_DIVISOR", 1)); + gGameExternalOptions.ubCoweringPenaltyDivisorCrouchedHead = __max(1,iniReader.ReadInteger("JA2 HAM Settings","CTH_PENALTY_FOR_COWERING_CROUCHED_TARGET_HEAD_DIVISOR", 3)); + gGameExternalOptions.ubCoweringPenaltyDivisorCrouchedTorso = __max(1,iniReader.ReadInteger("JA2 HAM Settings","CTH_PENALTY_FOR_COWERING_CROUCHED_TARGET_TORSO_DIVISOR", 4)); + gGameExternalOptions.ubCoweringPenaltyDivisorCrouchedLegs = __max(1,iniReader.ReadInteger("JA2 HAM Settings","CTH_PENALTY_FOR_COWERING_CROUCHED_TARGET_LEGS_DIVISOR", 5)); + + // HEADROCK HAM B2.8: This is the maximum range at which a target gives out the full CTH penalty for cowering. At lower range, it'll give proportionally less penalty. + gGameExternalOptions.usMinRangeForFullCoweringPenalty = __max(10,iniReader.ReadInteger("JA2 HAM Settings","MIN_RANGE_FOR_FULL_COWERING_TARGET_PENALTY", 100)); + + // HEADROCK HAM B2.8: Absolute maximum CTH penalty from target/shooter cowering + gGameExternalOptions.usMaxShooterCoweringPenalty = __max(1,iniReader.ReadInteger("JA2 HAM Settings","MAX_SHOOTER_COWERING_PENALTY", 100)); + gGameExternalOptions.usMaxTargetCoweringPenalty = __max(1,iniReader.ReadInteger("JA2 HAM Settings","MAX_TARGET_COWERING_PENALTY", 100)); + + // HEADROCK HAM B2.8: If this is turned on, Militia will drop their equipment similar to enemies, IF killed by non-player character. + gGameExternalOptions.ubMilitiaDropEquipment = iniReader.ReadInteger("JA2 HAM Settings","MILITIA_DROP_EQUIPMENT", 0); + + // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainees will go to sleep when their trainer goes to sleep. 3 = Trainer will go to sleep if all trainees are asleep. 1 = Both. 0 = Neither. + gGameExternalOptions.ubSmartTrainingSleep = iniReader.ReadInteger("JA2 HAM Settings","SMART_TRAINING-SLEEP_HANDLER", 0); + + // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainees will wake up when their trainer wakes up. 3 = Trainer will wake up if all trainees wake up. 1 = Both. 0 = Neither. + gGameExternalOptions.ubSmartTrainingWake = iniReader.ReadInteger("JA2 HAM Settings","SMART_TRAINING-WAKE_HANDLER", 0); + + // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainers will rest if no trainees available. 3 = Trainees will rest if no trainers available (not recommended). 1 = Both. 0 = Neither. + gGameExternalOptions.ubSmartTrainingRest = iniReader.ReadInteger("JA2 HAM Settings","SMART_TRAINING_REST", 0); } INT16 DynamicAdjustAPConstants(INT16 iniReadValue, INT16 iniDefaultValue, BOOLEAN reverse) diff --git a/GameSettings.h b/GameSettings.h index 2a726983..3ee1b5cc 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -486,7 +486,141 @@ BOOLEAN gbBulletTracer; INT32 fEnhancedDescriptionBox; // WANNE: Always use "prof.dat". - BOOLEAN fAlwaysUseProfDat; + BOOLEAN fAlwaysUseProfDat; + + // HEADROCK HAM B1: Global modifier for mine income (0 is automatically turned to 1 to avoid divide-by-zero.) + INT16 iMineIncomePercentage; + + // HEADROCK HAM B1: Allows changing the maximum CTH possible, for any attack 0-100 + UINT8 iMaximumCTH; + + // HEADROCK HAM B1: Allows changing the minimum CTH possible, for any attack 0-100 + UINT8 iMinimumCTH; + + // HEADROCK HAM B1: Allows reducing the chance of scoring a hit fractionally, if CTH = Minimum CTH + UINT8 iMinimumCTHDivisor; + + // HEADROCK HAM B1: Allows Restricted Roaming Militia to move through previously visited sectors, even if restricted. + BOOLEAN bUnrestrictVisited; + + // HEADROCK HAM B1: Allows the capture of towns to dynamically alter roaming restrictions + BOOLEAN bDynamicRestrictRoaming; + + // HEADROCK HAM B2: Reset suppression counter. 0 = never (oldskool), 1 = Every turn, 2 = Every attack. + UINT8 iClearSuppression; + + // HEADROCK HAM B2.1: This controls how effective suppression is, by increasing the number of ubSuppressionPoints accumulated by combatants (percentage); + INT16 iSuppressionEffectiveness; + + // HEADROCK HAM B2: MAXIMUM number of APs that can be lost to suppression in a given turn (0=unlimited) + UINT8 iMaxSuppressionAPLossPerTurn; + + // HEADROCK HAM B2: MAXIMUM number of APs that can be lost to suppression in a given attack (0=unlimited) + UINT8 iMaxSuppressionAPLossPerAttack; + + // HEADROCK HAM B2: Rigid limit to which a soldier's APs can drop due to suppression fire. (0=unlimited) + INT8 iMinAPLimitFromSuppression; + + // HEADROCK HAM B2: Suppression Shock ON/OFF + BOOLEAN fSuppressionShock; + + // HEADROCK HAM B2: Suppression Shock effectiveness (percentage, 100 = "normal", 0 = deactivated. Range 0-65535) + UINT16 iSuppressionShockEffectiveness; + + // HEADROCK HAM B2.1: CTH penalty given by a "Cowering" target to any enemy shooter. + INT8 iAimPenaltyPerTargetShock; + + // HEADROCK HAM B2.3: It is now possible for "cowering" to increase a target's suppression. + UINT16 iCowerEffectOnSuppression; + + // HEADROCK HAM B2.4: How many APs must be lost due to suppression for every 1 point of morale lost? + UINT8 iAPLostPerMoraleDrop; + + // HEADROCK HAM B2.5: Turn on Realistic Tracers. 0 = off (regular tracers). 1 = Fully realistic tracers. 2 = Tracer Bump + 1.13 Autofire Penalty Reduction + UINT8 iRealisticTracers; + + // HEADROCK HAM B2.5: Realistic tracers - one of every X bullets in a tracer magazines will be a tracer bullet. 0 = off (JA2 normal) + UINT8 iNumBulletsPerTracer; + + // HEADROCK HAM B2.5: Realistic tracers - CTH increased by this amount whenever a tracer is fired. 0 = off. + INT16 iCTHBumpPerTracer; + + // HEADROCK HAM B2.6: Increased aiming costs? + BOOLEAN fIncreasedAimingCost; + + // HEADROCK HAM B2.6: Dynamically determine Max-Aiming based also on weapon, bipod, etc? + BOOLEAN fDynamicAimingTime; + + // HEADROCK HAM B2.6: Controls how much effect target movement has on aiming + FLOAT iMovementEffectOnAiming; + + // HEADROCK HAM B2.6: Autofire Bullets/5AP modifier + INT16 iAutofireBulletsPer5APModifier; + + // HEADROCK HAM B2.6/B1: Adjustable "luck" factor in Auto-Resolve + FLOAT iAutoResolveLuckFactor; + + // HEADROCK HAM B2.6: Adjustable maximum for Suppression Shock effect. This has the added effect of reducing overall susceptibility to shock and may render some enemies unshockable. + UINT8 iMaxSuppressionShock; + + // HEADROCK HAM B2.6/2/1: Toggle new Burst/Auto CTH bars: 0=neither, 1=both, 2=Burst, 3=Auto + UINT8 iNewCTHBars; + + // HEADROCK HAM B2.6: Toggle whether AI checks for larger magazine when wanting to suppress at a distance + BOOLEAN fIncreaseAISuppressionFire; + + // HEADROCK HAM B2.7: Change the speed of skill progression. (defaults set to JA2 normal) + UINT16 ubHealthSubpointsToImprove; + UINT16 ubStrengthSubpointsToImprove; + UINT16 ubDexteritySubpointsToImprove; + UINT16 ubAgilitySubpointsToImprove; + UINT16 ubWisdomSubpointsToImprove; + UINT16 ubMarksmanshipSubpointsToImprove; + UINT16 ubMedicalSubpointsToImprove; + UINT16 ubMechanicalSubpointsToImprove; + UINT16 ubExplosivesSubpointsToImprove; + UINT16 ubLeadershipSubpointsToImprove; + UINT16 ubLevelSubpointsToImprove; + + // HEADROCK HAM B2.7: When turned on, this will give a CTH approximation instead of an exact value, on CTH Bars and "F" key feedback. + BOOLEAN fApproximateCTH; + + // HEADROCK HAM B2.7: Augmented Roaming Militia code - turn this to TRUE to allow militia free travel through San Mona, Tixa, Orta, Omerta, and Estoni. + BOOLEAN fAllowMilitiaMoveThroughMinorCities; + + // HEADROCK HAM B2.7: Smarter Roaming Militia Generator will generate Roamers in any available city perimeter square, allowing us to avoid "wasting" training sessions more easily. + BOOLEAN fSmartRoamingMilitiaGenerator; + + // HEADROCK HAM B2.7: When "TRUE", Roaming Militia will be created on a 25% Veteran, 25% Regular, 50% green basis. If new militia is placed into a full group, it will upgrade greens/regulars in that group. + BOOLEAN fDiverseRoamingMilitiaGroups; + + // HEADROCK HAM B2.8: This setting controls whether militia will try to join up into the largest groups they can, or average out the size of the groups to cover more territory. + UINT8 ubRoamingMilitiaSpreadsOutChance; + + // HEADROCK HAM B2.8: These are new cowering penalty divisors that help us determine how effective cowering is in different stances and when the shooter is targetting different bodyparts + UINT8 ubCoweringPenaltyDivisorProne; + UINT8 ubCoweringPenaltyDivisorCrouchedHead; + UINT8 ubCoweringPenaltyDivisorCrouchedTorso; + UINT8 ubCoweringPenaltyDivisorCrouchedLegs; + + // HEADROCK HAM B2.8: This is the maximum range at which a target gives out the full CTH penalty for cowering. At lower range, it'll give proportionally less penalty. + UINT16 usMinRangeForFullCoweringPenalty; + + // HEADROCK HAM B2.8: Absolute maximum CTH penalty from target/shooter cowering + UINT16 usMaxShooterCoweringPenalty; + UINT16 usMaxTargetCoweringPenalty; + + // HEADROCK HAM B2.8: If this is turned on, Militia will drop their equipment similar to enemies, IF killed by non-player character. + UINT8 ubMilitiaDropEquipment; + + // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainees will go to sleep when their trainer goes to sleep. 3 = Trainer will go to sleep if all trainees are asleep. 1 = Both. 0 = Neither. + UINT8 ubSmartTrainingSleep; + + // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainees will wake up when their trainer wakes up. 3 = Trainer will wake up if all trainees wake up. 1 = Both. 0 = Neither. + UINT8 ubSmartTrainingWake; + + // HEADROCK HAM B2.8: New Trainer Relations: 2 = Trainers will rest if no trainees available. 3 = Trainees will rest if no trainers available (not recommended). 1 = Both. 0 = Neither. + UINT8 ubSmartTrainingRest; } GAME_EXTERNAL_OPTIONS; diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index ce23d6bc..8371d3fe 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -105,6 +105,8 @@ #include "Assignments.h" #include "Interface Items.h" #include "Shopkeeper Interface.h" + // HEADROCK HAM B1: Additional Include for HAM + #include "MilitiaSquads.h" #endif #include "BobbyR.h" @@ -4401,6 +4403,11 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) // fix squads CheckSquadMovementGroups(); + // HEADROCK HAM B1: Re-Adjust Dynamic Roaming Militia restrictions + if (gGameExternalOptions.bDynamicRestrictRoaming) + { + AdjustRoamingRestrictions(); + } //The above function LightSetBaseLevel adjusts ALL the level node light values including the merc node, //we must reset the values diff --git a/Strategic/Assignments.cpp b/Strategic/Assignments.cpp index 12140efa..b9ffa23f 100644 --- a/Strategic/Assignments.cpp +++ b/Strategic/Assignments.cpp @@ -105,10 +105,12 @@ enum { REPAIR_BIG_POCKETS, REPAIR_MED_POCKETS, REPAIR_SML_POCKETS, + REPAIR_LBE_GEAR, // HEADROCK HAM B2.8: New pass type for fixing LBEs only NUM_REPAIR_PASS_TYPES, }; -#define FINAL_REPAIR_PASS REPAIR_SML_POCKETS +// HEADROCK HAM B2.8: Changed LBEs to be the final pass +#define FINAL_REPAIR_PASS REPAIR_LBE_GEAR /* CHRISL: bSlot[xx] array declaration needs to reflect largest number of inventory locations. New inventory @@ -128,6 +130,7 @@ REPAIR_PASS_SLOTS_TYPE gRepairPassSlotList[ NUM_REPAIR_PASS_TYPES ] = { /* big pockets */ 4, 7, BIGPOCK1POS, BIGPOCK2POS, BIGPOCK3POS, BIGPOCK4POS, BIGPOCK5POS, BIGPOCK6POS, BIGPOCK7POS, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { /* med pockets */ 0, 4, MEDPOCK1POS, MEDPOCK2POS, MEDPOCK3POS, MEDPOCK4POS, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { /* sml pockets */ 8, 30, SMALLPOCK1POS, SMALLPOCK2POS, SMALLPOCK3POS, SMALLPOCK4POS, SMALLPOCK5POS, SMALLPOCK6POS, SMALLPOCK7POS, SMALLPOCK8POS, SMALLPOCK9POS, SMALLPOCK10POS, SMALLPOCK11POS, SMALLPOCK12POS, SMALLPOCK13POS, SMALLPOCK14POS, SMALLPOCK15POS, SMALLPOCK16POS, SMALLPOCK17POS, SMALLPOCK18POS, SMALLPOCK19POS, SMALLPOCK20POS, SMALLPOCK21POS, SMALLPOCK22POS, SMALLPOCK23POS, SMALLPOCK24POS, SMALLPOCK25POS, SMALLPOCK26POS, SMALLPOCK27POS, SMALLPOCK28POS, SMALLPOCK29POS, SMALLPOCK30POS }, + { /* HEADROCK HAM B2.8: LBE Slot pass */ 0, 5, VESTPOCKPOS, LTHIGHPOCKPOS, RTHIGHPOCKPOS, CPACKPOCKPOS, BPACKPOCKPOS, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, }; extern STR16 sRepairsDoneString[]; @@ -3298,18 +3301,42 @@ void HandleRepairBySoldier( SOLDIERTYPE *pSoldier ) fAnyOfSoldiersOwnItemsWereFixed = UnjamGunsOnSoldier( pSoldier, pSoldier, &ubRepairPtsLeft ); // repair items on self - for( bLoop = 0; bLoop < 2; bLoop++ ) + // HEADROCK HAM B2.8: Experimental feature: Fixes LBEs last, as they don't actually require repairs. + for( bLoop = 0; bLoop < 4; bLoop++ ) { if ( bLoop == 0 ) { bLoopStart = SECONDHANDPOS; + // HEADROCK: New loop stage only checks second hand, to avoid LBEs. + bLoopEnd = SECONDHANDPOS; + } + else if ( bLoop == 1 ) + { + // HEADROCK: Second check is for armor and headgear only. + bLoopStart = HELMETPOS; + bLoopEnd = HEAD2POS; + } + else if ( bLoop == 2 ) + { + // HEADROCK: Loop stage altered to run through inventory only + bLoopStart = UsingNewInventorySystem() == false ? BIGPOCKSTART : GUNSLINGPOCKPOS; // CHRISL: Changed to dynamically determine max inventory locations. bLoopEnd = (NUM_INV_SLOTS - 1); } - else + else if ( bLoop == 3 ) { - bLoopStart = HELMETPOS; - bLoopEnd = UsingNewInventorySystem() == false ? HEAD2POS : BPACKPOCKPOS; + if (UsingNewInventorySystem() == true) + { + // HEADROCK: Last loop fixes LBEs + bLoopStart = VESTPOCKPOS; + bLoopEnd = BPACKPOCKPOS; + } + else + { + // HEADROCK: In OIV, simply check everything again. + bLoopStart = SECONDHANDPOS; + bLoopEnd = (NUM_INV_SLOTS - 1); + } } // now repair objects running from left hand to small pocket @@ -3317,10 +3344,12 @@ void HandleRepairBySoldier( SOLDIERTYPE *pSoldier ) { //CHRISL: These two conditions allow us to repair LBE pocket items at the same time as worn armor, while // still letting us repair the item in our offhand first. - if(UsingNewInventorySystem() == true && bLoop == 0 && bPocket>SECONDHANDPOS && bPocketSECONDHANDPOS && bPocketinv[ bPocket ]); if ( RepairObject( pSoldier, pSoldier, pObj, &ubRepairPtsLeft ) ) @@ -3693,7 +3722,17 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) } // now finally train the grunt - TrainSoldierWithPts( pStudent, sTotalTrainingPts ); + // HEADROCK HAM B2.8: A new trainer/student synch system allows students to rest while + // their trainer is asleep. If this happens, the student should not train on their own! + if (gGameExternalOptions.ubSmartTrainingRest == 0 || gGameExternalOptions.ubSmartTrainingRest == 2) + { + TrainSoldierWithPts( pStudent, sTotalTrainingPts ); + } + else if ( pTrainer != NULL ) + { + // This only occurs if at least one trainer is awake. + TrainSoldierWithPts( pStudent, sTotalTrainingPts ); + } } } } @@ -5587,13 +5626,17 @@ void MakeSureToolKitIsInHand( SOLDIERTYPE *pSoldier ) { if( Item[pSoldier->inv[ bPocket ].usItem].toolkit ) { - // If the second hand is empty, swap item in first hand there - if (pSoldier->inv[HANDPOS].exists() && - !pSoldier->inv[SECONDHANDPOS].exists() && - CanItemFitInPosition(pSoldier, &pSoldier->inv[SECONDHANDPOS], HANDPOS, FALSE)) { - SwapObjs( pSoldier, HANDPOS, SECONDHANDPOS, TRUE ); - } - if(!CanItemFitInPosition(pSoldier, &pSoldier->inv[HANDPOS], bPocket, FALSE)) + // HEADROCK HAM B2.8: These new conditions will create a bias for swapping an item out of + // our hand. + + //If the second hand is free, the item will go to the SECONDHANDPOS while the toolkit + // goes into the HANDPOS + if( Item[pSoldier->inv[HANDPOS].usItem].usItemClass & (IC_WEAPON | IC_PUNCH) && !pSoldier->inv[SECONDHANDPOS].exists()) + SwapObjs(pSoldier, HANDPOS, SECONDHANDPOS, TRUE); + // Else, if the gun sling slot is free, and the item can go there, it will. + else if(UsingNewInventorySystem() && !pSoldier->inv[GUNSLINGPOCKPOS].exists() && CanItemFitInPosition(pSoldier, &pSoldier->inv[HANDPOS], GUNSLINGPOCKPOS, FALSE)) + SwapObjs(pSoldier, HANDPOS, GUNSLINGPOCKPOS, TRUE); + else if(!CanItemFitInPosition(pSoldier, &pSoldier->inv[HANDPOS], bPocket, FALSE)) SwapObjs(pSoldier, HANDPOS, SECONDHANDPOS, TRUE); SwapObjs( pSoldier, HANDPOS, bPocket, TRUE ); break; @@ -5623,13 +5666,17 @@ BOOLEAN MakeSureMedKitIsInHand( SOLDIERTYPE *pSoldier ) if ( Item[pSoldier->inv[ bPocket ].usItem].medicalkit ) { fCharacterInfoPanelDirty = TRUE; - // If the second hand is empty, swap item in first hand there - if (pSoldier->inv[HANDPOS].exists() && - !pSoldier->inv[SECONDHANDPOS].exists() && - CanItemFitInPosition(pSoldier, &pSoldier->inv[SECONDHANDPOS], HANDPOS, FALSE)) { - SwapObjs( pSoldier, HANDPOS, SECONDHANDPOS, TRUE ); - } - if(!CanItemFitInPosition(pSoldier, &pSoldier->inv[HANDPOS], bPocket, FALSE)) + // HEADROCK HAM B2.8: These new conditions will create a bias for swapping an item out of + // our hand. + + //If the second hand is free, the item will go to the SECONDHANDPOS while the medikit + // goes into the HANDPOS + if( Item[pSoldier->inv[HANDPOS].usItem].usItemClass & (IC_WEAPON | IC_PUNCH) && !pSoldier->inv[SECONDHANDPOS].exists()) + SwapObjs(pSoldier, HANDPOS, SECONDHANDPOS, TRUE); + // Else, if the gun sling slot is free, and the item can go there, it will. + else if(UsingNewInventorySystem() && !pSoldier->inv[GUNSLINGPOCKPOS].exists() && CanItemFitInPosition(pSoldier, &pSoldier->inv[HANDPOS], GUNSLINGPOCKPOS, FALSE)) + SwapObjs(pSoldier, HANDPOS, GUNSLINGPOCKPOS, TRUE); + else if(!CanItemFitInPosition(pSoldier, &pSoldier->inv[HANDPOS], bPocket, FALSE)) SwapObjs(pSoldier, HANDPOS, SECONDHANDPOS, TRUE); SwapObjs( pSoldier, HANDPOS, bPocket, TRUE ); return(TRUE); @@ -11719,9 +11766,357 @@ BOOLEAN UnjamGunsOnSoldier( SOLDIERTYPE *pOwnerSoldier, SOLDIERTYPE *pRepairSold return ( fAnyGunsWereUnjammed ); } +// HEADROCK HAM B2.8: A set of functions to synchronize sleeping periods of trainers and trainees +BOOLEAN SetTrainerSleepWhenTraineesSleep( SOLDIERTYPE *pThisTrainee) +{ + UINT16 sMapX = pThisTrainee->sSectorX; + UINT16 sMapY = pThisTrainee->sSectorY; + UINT16 sMapZ = pThisTrainee->sZLevel; + UINT8 bStat = pThisTrainee->bTrainStat; + INT32 iCounter, iNumberOnTeam; + + SOLDIERTYPE * pOtherTrainee; + SOLDIERTYPE * pTrainer; + BOOLEAN fAllTraineesAsleep = TRUE; + BOOLEAN fTrainersSentToSleep = FALSE; + + if (pThisTrainee->bAssignment != TRAIN_BY_OTHER) + { + // Shouldn't happen... + return (FALSE); + } + + iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; + + // Check to see if all other trainees of the same stat are also asleep + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pOtherTrainee = &Menptr[ iCounter ]; + if (pOtherTrainee->bAssignment == TRAIN_BY_OTHER && pOtherTrainee->bTrainStat == pThisTrainee->bTrainStat && + pOtherTrainee->sSectorX == sMapX && pOtherTrainee->sSectorY == sMapY && pOtherTrainee->sZLevel == sMapZ && + pOtherTrainee->bActive && !pOtherTrainee->flags.fMercAsleep ) + { + // Trainee is present and awake. Flag is reset to false. + fAllTraineesAsleep = FALSE; + } + } + // If they are all asleep + if (fAllTraineesAsleep) + { + // Look for trainers of that stat, in the same sector + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pTrainer = &Menptr[ iCounter ]; + if (pTrainer->bAssignment == TRAIN_TEAMMATE && pTrainer->bTrainStat == pThisTrainee->bTrainStat && + pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->sZLevel == sMapZ && + pTrainer->bActive && !pTrainer->flags.fMercAsleep ) + { + // Trainer will go to sleep + if( SetMercAsleep( pTrainer, FALSE ) ) + { + if( gGameSettings.fOptions[ TOPTION_SLEEPWAKE_NOTIFICATION ] ) + { + // tell player about it + AddSoldierToWaitingListQueue( pTrainer ); + } + + // seems unnecessary now? ARM + pTrainer->bOldAssignment = pTrainer->bAssignment; + + fTrainersSentToSleep = TRUE; + } + } + } + if (fTrainersSentToSleep) + { + return(TRUE); + } + else + { + return(FALSE); + } + } + else + { + return(FALSE); + } +} +BOOLEAN SetTraineesSleepWhenTrainerSleeps( SOLDIERTYPE *pTrainer) +{ + UINT16 sMapX = pTrainer->sSectorX; + UINT16 sMapY = pTrainer->sSectorY; + UINT16 sMapZ = pTrainer->sZLevel; + UINT8 bStat = pTrainer->bTrainStat; + INT32 iCounter, iNumberOnTeam; + BOOLEAN fTraineesSentToSleep = FALSE; + + SOLDIERTYPE * pTrainee; + + if (pTrainer->bAssignment != TRAIN_TEAMMATE) + { + // Shouldn't happen... + return(FALSE); + } + + iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; + + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pTrainee = &Menptr[ iCounter ]; + if (pTrainee->bAssignment == TRAIN_BY_OTHER && pTrainee->bTrainStat == pTrainer->bTrainStat && + pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->sZLevel == sMapZ && + pTrainee->bActive && !pTrainee->flags.fMercAsleep ) + { + // Trainee will go to sleep + if( SetMercAsleep( pTrainee, FALSE ) ) + { + if( gGameSettings.fOptions[ TOPTION_SLEEPWAKE_NOTIFICATION ] ) + { + // tell player about it + AddSoldierToWaitingListQueue( pTrainee ); + } + + // seems unnecessary now? ARM + pTrainee->bOldAssignment = pTrainee->bAssignment; + + fTraineesSentToSleep = TRUE; + } + } + } + + if (fTraineesSentToSleep) + { + return(TRUE); + } + else + { + return(FALSE); + } +} +BOOLEAN SetTrainerWakeWhenTraineesWake( SOLDIERTYPE *pThisTrainee) +{ + UINT16 sMapX = pThisTrainee->sSectorX; + UINT16 sMapY = pThisTrainee->sSectorY; + UINT16 sMapZ = pThisTrainee->sZLevel; + UINT8 bStat = pThisTrainee->bTrainStat; + INT32 iCounter, iNumberOnTeam; + + SOLDIERTYPE * pOtherTrainee; + SOLDIERTYPE * pTrainer; + BOOLEAN fAllTraineesAwake = TRUE; + BOOLEAN fTrainersWokenUp = FALSE; + if (pThisTrainee->bAssignment != TRAIN_BY_OTHER) + { + // Shouldn't happen... + return (FALSE); + } + iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; + + // Check to see if all other trainees of the same stat are also asleep + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pOtherTrainee = &Menptr[ iCounter ]; + if (pOtherTrainee->bAssignment == TRAIN_BY_OTHER && pOtherTrainee->bTrainStat == pThisTrainee->bTrainStat && + pOtherTrainee->sSectorX == sMapX && pOtherTrainee->sSectorY == sMapY && pOtherTrainee->sZLevel == sMapZ && + pOtherTrainee->bActive && pOtherTrainee->flags.fMercAsleep ) + { + // Trainee is present and asleep. Flag is reset to FALSE. + fAllTraineesAwake = FALSE; + } + } + // If they are all awake + if (fAllTraineesAwake) + { + // Look for trainers of that stat, in the same sector + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pTrainer = &Menptr[ iCounter ]; + if (pTrainer->bAssignment == TRAIN_TEAMMATE && pTrainer->bTrainStat == pThisTrainee->bTrainStat && + pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->sZLevel == sMapZ && + pTrainer->bActive && pTrainer->flags.fMercAsleep ) + { + // Trainer will wake up + if( SetMercAwake( pTrainer, FALSE, FALSE ) ) + { + if( gGameSettings.fOptions[ TOPTION_SLEEPWAKE_NOTIFICATION ] ) + { + // tell player about it + AddSoldierToWaitingListQueue( pTrainer ); + } + + // seems unnecessary now? ARM + pTrainer->bOldAssignment = pTrainer->bAssignment; + + fTrainersWokenUp = TRUE; + } + } + } + if (fTrainersWokenUp) + { + return(TRUE); + } + else + { + return(FALSE); + } + } + else + { + return(FALSE); + } +} + +BOOLEAN SetTraineesWakeWhenTrainerWakes( SOLDIERTYPE *pTrainer) +{ + UINT16 sMapX = pTrainer->sSectorX; + UINT16 sMapY = pTrainer->sSectorY; + UINT16 sMapZ = pTrainer->sZLevel; + UINT8 bStat = pTrainer->bTrainStat; + INT32 iCounter, iNumberOnTeam; + BOOLEAN fTraineesWokenUp = FALSE; + + SOLDIERTYPE * pTrainee; + + if (pTrainer->bAssignment != TRAIN_TEAMMATE) + { + // Shouldn't happen... + return(FALSE); + } + + iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; + + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pTrainee = &Menptr[ iCounter ]; + if (pTrainee->bAssignment == TRAIN_BY_OTHER && pTrainee->bTrainStat == pTrainer->bTrainStat && + pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->sZLevel == sMapZ && + pTrainee->bActive && pTrainee->flags.fMercAsleep ) + { + // Trainee will wake up + if( SetMercAwake( pTrainee, FALSE, FALSE ) ) + { + if( gGameSettings.fOptions[ TOPTION_SLEEPWAKE_NOTIFICATION ] ) + { + // tell player about it + AddSoldierToWaitingListQueue( pTrainee ); + } + + // seems unnecessary now? ARM + pTrainee->bOldAssignment = pTrainee->bAssignment; + + fTraineesWokenUp = TRUE; + } + } + } + + if (fTraineesWokenUp) + { + return(TRUE); + } + else + { + return(FALSE); + } +} + +void HandleTrainingSleepSynchronize( SOLDIERTYPE *pSoldier ) +{ + // HEADROCK HAM B2.8: Trainees will now go to sleep if the trainer goes to sleep. + if ((gGameExternalOptions.ubSmartTrainingSleep == 1 || gGameExternalOptions.ubSmartTrainingSleep == 2) && pSoldier->bAssignment == TRAIN_TEAMMATE) + { + SetTraineesSleepWhenTrainerSleeps( pSoldier ); + } + + // HEADROCK HAM B2.8: If this is a trainee, and all other trainees are already asleep, put all trainers to sleep as well. + if ( (gGameExternalOptions.ubSmartTrainingSleep == 1 || gGameExternalOptions.ubSmartTrainingSleep == 3 ) && pSoldier->bAssignment == TRAIN_BY_OTHER) + { + SetTrainerSleepWhenTraineesSleep( pSoldier ); + } +} + +void HandleTrainingWakeSynchronize( SOLDIERTYPE *pSoldier ) +{ + // HEADROCK HAM B2.8: Trainees will now go to sleep if the trainer goes to sleep. + if ((gGameExternalOptions.ubSmartTrainingWake == 1 || gGameExternalOptions.ubSmartTrainingWake == 2) && pSoldier->bAssignment == TRAIN_TEAMMATE) + { + SetTraineesWakeWhenTrainerWakes( pSoldier ); + } + + // HEADROCK HAM B2.8: If this is a trainee, and all other trainees are already asleep, put all trainers to sleep as well. + if ( (gGameExternalOptions.ubSmartTrainingWake == 1 || gGameExternalOptions.ubSmartTrainingWake == 3 ) && pSoldier->bAssignment == TRAIN_BY_OTHER) + { + SetTrainerWakeWhenTraineesWake( pSoldier ); + } +} + +BOOLEAN FindAnyAwakeTrainers( SOLDIERTYPE *pTrainee ) +{ + UINT16 sMapX = pTrainee->sSectorX; + UINT16 sMapY = pTrainee->sSectorY; + UINT16 sMapZ = pTrainee->sZLevel; + UINT8 bStat = pTrainee->bTrainStat; + INT32 iCounter, iNumberOnTeam; + BOOLEAN fAllTrainersAsleep = TRUE; + + SOLDIERTYPE * pTrainer; + + if (pTrainee->bAssignment != TRAIN_BY_OTHER) + { + // Shouldn't happen... + return(FALSE); + } + + iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; + + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pTrainer = &Menptr[ iCounter ]; + if (pTrainer->bAssignment == TRAIN_TEAMMATE && pTrainer->bTrainStat == pTrainee->bTrainStat && + pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->sZLevel == sMapZ && + pTrainer->bActive && !pTrainer->flags.fMercAsleep ) + { + fAllTrainersAsleep = FALSE; + } + } + + return(fAllTrainersAsleep); +} + +BOOLEAN FindAnyAwakeTrainees( SOLDIERTYPE *pTrainer ) +{ + UINT16 sMapX = pTrainer->sSectorX; + UINT16 sMapY = pTrainer->sSectorY; + UINT16 sMapZ = pTrainer->sZLevel; + UINT8 bStat = pTrainer->bTrainStat; + INT32 iCounter, iNumberOnTeam; + BOOLEAN fAllTraineesAsleep = TRUE; + + SOLDIERTYPE * pTrainee; + + if (pTrainer->bAssignment != TRAIN_TEAMMATE) + { + // Shouldn't happen... + return(FALSE); + } + + iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; + + for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + { + pTrainee = &Menptr[ iCounter ]; + if (pTrainee->bAssignment == TRAIN_BY_OTHER && pTrainee->bTrainStat == pTrainer->bTrainStat && + pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->sZLevel == sMapZ && + pTrainee->bActive && !pTrainee->flags.fMercAsleep ) + { + fAllTraineesAsleep = FALSE; + } + } + + return(fAllTraineesAsleep); +} diff --git a/Strategic/Assignments.h b/Strategic/Assignments.h index 07b2c70d..617c3bca 100644 --- a/Strategic/Assignments.h +++ b/Strategic/Assignments.h @@ -324,6 +324,15 @@ void UnEscortEPC( SOLDIERTYPE *pSoldier ); SOLDIERTYPE *AnyDoctorWhoCanHealThisPatient( SOLDIERTYPE *pPatient, BOOLEAN fThisHour ); +// HEADROCK HAM B2.8: A set of functions to synchronize sleeping periods of trainers and trainees +BOOLEAN SetTrainerSleepWhenTraineesSleep( SOLDIERTYPE *pTrainee); +BOOLEAN SetTraineesSleepWhenTrainerSleeps( SOLDIERTYPE *pTrainer ); +BOOLEAN SetTrainerWakeWhenTraineesWake( SOLDIERTYPE *pTrainee ); +BOOLEAN SetTraineesWakeWhenTrainerWakes( SOLDIERTYPE *pTrainer ); +void HandleTrainingSleepSynchronize( SOLDIERTYPE *pSoldier ); +void HandleTrainingWakeSynchronize( SOLDIERTYPE *pSoldier ); +BOOLEAN FindAnyAwakeTrainers( SOLDIERTYPE *pTrainee ); +BOOLEAN FindAnyAwakeTrainees( SOLDIERTYPE *pTrainer ); #endif diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index 68c83c9e..c59f961e 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -4007,7 +4007,7 @@ void AttackTarget( SOLDIERCELL *pAttacker, SOLDIERCELL *pTarget ) pAttacker->uiFlags |= CELL_FIREDATTARGET | CELL_DIRTY; if( pAttacker->usAttack < 950 ) - usAttack = (UINT16)(pAttacker->usAttack + PreRandom(2000 - pAttacker->usAttack )); + usAttack = (UINT16)(pAttacker->usAttack + PreRandom((INT16)(gGameExternalOptions.iAutoResolveLuckFactor*1000.0) - pAttacker->usAttack )); else usAttack = (UINT16)(950 + PreRandom( 50 )); if( pTarget->uiFlags & CELL_RETREATING && !(pAttacker->uiFlags & CELL_FEMALECREATURE) ) @@ -4016,7 +4016,7 @@ void AttackTarget( SOLDIERCELL *pAttacker, SOLDIERCELL *pTarget ) usAttack = usAttack * 7 / 10; } if( pTarget->usDefence < 950 ) - usDefence = (UINT16)(pTarget->usDefence + PreRandom(2000 - pTarget->usDefence )); + usDefence = (UINT16)(pTarget->usDefence + PreRandom((INT16)(gGameExternalOptions.iAutoResolveLuckFactor*1000.0) - pTarget->usDefence )); else usDefence = (UINT16)(950 + PreRandom( 50 )); if( pAttacker->uiFlags & CELL_FEMALECREATURE ) diff --git a/Strategic/MilitiaSquads.cpp b/Strategic/MilitiaSquads.cpp index 4fa1f73b..0bd06fa2 100644 --- a/Strategic/MilitiaSquads.cpp +++ b/Strategic/MilitiaSquads.cpp @@ -66,6 +66,10 @@ INT32 iRestrictedSectorArraySize; UINT32 gRestrictMilitia[256]; +// HEADROCK HAM B1: Alternate array keeps track of dynamically unrestricted sectors +BOOLEAN gDynamicRestrictMilitia[ 256 ]; +// HEADROCK HAM B1: Function that dynamically unrestricts sectors as we take over towns. +extern void AdjustRoamingRestrictions(); UINT8 gpAttackDirs[5][4]; // 0. Green Militia 1. Regular Militia 2. Elite Militia 3. Insertion code UINT8 guiDirNumber = 0; @@ -143,9 +147,41 @@ void GenerateMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY ) { SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); - StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] / gGameExternalOptions.guiDivOfOriginalMilitia); - StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] / gGameExternalOptions.guiDivOfOriginalMilitia); - StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] / gGameExternalOptions.guiDivOfOriginalMilitia); + //HEADROCK HAM B2.7: Track the number of militia at the source. + UINT16 bTotalMilitiaAtSource = pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] + pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] + pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + UINT8 bTargetGreen, bTargetRegular, bTargetElite; + UINT8 bUpgradePoints; + + if (gGameExternalOptions.fDiverseRoamingMilitiaGroups) + { + bTargetRegular = __max(1,bTotalMilitiaAtSource / (gGameExternalOptions.guiDivOfOriginalMilitia * 4)); + bTargetElite = __max(1,bTotalMilitiaAtSource / (gGameExternalOptions.guiDivOfOriginalMilitia * 4)); + bTargetGreen = (bTotalMilitiaAtSource / gGameExternalOptions.guiDivOfOriginalMilitia) - (bTargetRegular + bTargetElite); + + pSectorInfo = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] ); + + // Continue if we're dealing with a full group that isn't full of veterans + if ( CountMilitia(pSectorInfo) >= gGameExternalOptions.guiMaxMilitiaSquadSize && pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] < CountMilitia(pSectorInfo) ) + { + bUpgradePoints = bTargetGreen + (bTargetRegular * 2); + bTargetElite += __min(bTargetRegular, pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]); + bUpgradePoints -= (__min(bTargetRegular, pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]))*2; + bTargetRegular = bUpgradePoints; + bTargetGreen = 0; + } + + // HEADROCK HAM B2.7: Create 1/2 green, 1/4 regular, 1/4 elite. + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, bTargetGreen); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, bTargetRegular); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, bTargetElite); + } + + else + { + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] / gGameExternalOptions.guiDivOfOriginalMilitia); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] / gGameExternalOptions.guiDivOfOriginalMilitia); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] / gGameExternalOptions.guiDivOfOriginalMilitia); + } pSectorInfo = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] ); @@ -178,13 +214,145 @@ void MoveMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY, BOOL SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); SECTORINFO *pTSectorInfo = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] ); - StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); - StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); - StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + UINT8 bGreensSourceTeam = 0, bGreensDestTeam = 0; + UINT8 bRegularsSourceTeam = 0, bRegularsDestTeam = 0; + UINT8 bElitesSourceTeam = 0, bElitesDestTeam = 0; + UINT8 bTotalGreens = 0, bTotalRegulars = 0, bTotalElites = 0; + UINT8 bTotalGreensPercent = 0, bTotalRegularsPercent = 0, bTotalElitesPercent = 0; + INT8 bNewSourceGroupSize = 0, bNewDestGroupSize = 0, bGroupSizeRatio = 0; - StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); - StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); - StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + // HEADROCK HAM B2.8 + // There are now several methods of militia join-up. + if (PreRandom(100) < gGameExternalOptions.ubRoamingMilitiaSpreadsOutChance) + { + // Militia will attempt to spread out, averaging the size and composition of both groups. + if ( CountMilitia(pSectorInfo) && CountMilitia(pTSectorInfo) ) + { + bGreensSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + bGreensDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + bRegularsSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + bRegularsDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + bElitesSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + bElitesDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + + bTotalGreens = bGreensSourceTeam + bGreensDestTeam; + bTotalRegulars = bRegularsSourceTeam + bRegularsDestTeam; + bTotalElites = bElitesSourceTeam + bElitesDestTeam; + + // Source team gets half of each group, rounded down + bGreensSourceTeam = bTotalGreens / 2; + bRegularsSourceTeam = bTotalRegulars / 2; + bElitesSourceTeam = bTotalElites / 2; + + // Destination team gets half of each group, rounded up + bGreensDestTeam = bTotalGreens - bGreensSourceTeam; + bRegularsDestTeam = bTotalRegulars - bRegularsSourceTeam; + bElitesDestTeam = bTotalElites - bElitesSourceTeam; + + // Erase ALL militia from both locations. + StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sTMapX, sTMapY, GREEN_MILITIA, pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sTMapX, sTMapY, REGULAR_MILITIA, pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sTMapX, sTMapY, ELITE_MILITIA, pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + + // Add new militia. + StrategicAddMilitiaToSector( sMapX, sMapY, GREEN_MILITIA, bGreensSourceTeam ); + StrategicAddMilitiaToSector( sMapX, sMapY, REGULAR_MILITIA, bRegularsSourceTeam ); + StrategicAddMilitiaToSector( sMapX, sMapY, ELITE_MILITIA, bElitesSourceTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, bGreensDestTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, bRegularsDestTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, bElitesDestTeam ); + } + else + { + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + + StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + } + } + else + { + if ( gGameExternalOptions.fDiverseRoamingMilitiaGroups ) + { + // Diverse Militia Join-up: Destination team gets as many troops as possible, but both teams + // keep an averaged green-regular-elite ratio. + bNewDestGroupSize = __min((CountMilitia(pTSectorInfo) + CountMilitia(pSectorInfo)), (UINT8)gGameExternalOptions.guiMaxMilitiaSquadSize); + bNewSourceGroupSize = __max(0,(CountMilitia(pTSectorInfo) + CountMilitia(pSectorInfo)) - gGameExternalOptions.guiMaxMilitiaSquadSize); + + // If there are still going to be two teams after the transfer + if ( bNewSourceGroupSize > 0 ) + { + + bGreensSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + bGreensDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + bRegularsSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + bRegularsDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + bElitesSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + bElitesDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + + bTotalGreens = bGreensSourceTeam + bGreensDestTeam; + bTotalRegulars = bRegularsSourceTeam + bRegularsDestTeam; + bTotalElites = bElitesSourceTeam + bElitesDestTeam; + + bGroupSizeRatio = __max(1,(bNewDestGroupSize / bNewSourceGroupSize)); + + // Destination team gets the majority of troops (based on the ratios), + // but will always leave at least 1 troop of each type (if available) for the source team + bGreensDestTeam = __min(__min(bTotalGreens, ((bTotalGreens * bGroupSizeRatio) / (bGroupSizeRatio + 1) +1) ), __max(0,bTotalGreens-1)); + bRegularsDestTeam = __min(__min(bTotalRegulars, ((bTotalRegulars * bGroupSizeRatio) / (bGroupSizeRatio + 1) +1) ), __max(0,bTotalRegulars-1)); + bElitesDestTeam = __min(__min(bTotalElites, ((bTotalElites * bGroupSizeRatio) / (bGroupSizeRatio + 1) +1) ), __max(0,bTotalElites-1)); + + // Source team gets the remainder (no less than one troop of each type, if available) + bGreensSourceTeam = bTotalGreens - bGreensDestTeam; + bRegularsSourceTeam = bTotalRegulars - bRegularsDestTeam; + bElitesSourceTeam = bTotalElites - bElitesDestTeam; + + // Erase ALL militia from both locations. + StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sTMapX, sTMapY, GREEN_MILITIA, pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sTMapX, sTMapY, REGULAR_MILITIA, pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sTMapX, sTMapY, ELITE_MILITIA, pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + + // Add new militia. + StrategicAddMilitiaToSector( sMapX, sMapY, GREEN_MILITIA, bGreensSourceTeam ); + StrategicAddMilitiaToSector( sMapX, sMapY, REGULAR_MILITIA, bRegularsSourceTeam ); + StrategicAddMilitiaToSector( sMapX, sMapY, ELITE_MILITIA, bElitesSourceTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, bGreensDestTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, bRegularsDestTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, bElitesDestTeam ); + } + else + { + // Move all militia from source to destination. + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + + StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + } + } + else + { + // normal join-up. Destination group gets all the best militia. Source team gets the leftovers. + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + + StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] ); + } + } while( !fAlternativeMax && CountMilitia(pTSectorInfo) > gGameExternalOptions.guiMaxMilitiaSquadSize || fAlternativeMax && CountMilitia(pTSectorInfo) > gGameExternalOptions.guiMaxMilitiaSquadSizeBattle ) @@ -362,12 +530,40 @@ UINT16 CountDirectionRating( INT16 sMapX, INT16 sMapY, UINT8 uiDir ) BOOLEAN CheckStandardConditionsForDirection( INT16 sSMapX, INT16 sSMapY, INT16 sMapX, INT16 sMapY, BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ) { UINT8 uiTravab = GetTraversability( SECTOR( sSMapX , sSMapY ) , SECTOR( sMapX, sMapY ) ); + BOOLEAN fTargetCityAllowsRoaming; if( uiTravab == GROUNDBARRIER || uiTravab == EDGEOFWORLD ) return FALSE; if( !fForBattle && gfMSBattle && NumEnemiesInSector( sMapX, sMapY ) ) return FALSE; - return (GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR || fWithCities) && !IsThisSectorASAMSector( sMapX, sMapY , 0 ) && + // HEADROCK HAM B2.7 : This allows roaming militia to move into Minor City sectors, if liberated at least + // once before + if (GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR) + { + fTargetCityAllowsRoaming = TRUE; + } + else + { + if (gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities) + { + if ( SectorInfo[SECTOR( sMapX, sMapY )].fSurfaceWasEverPlayerControlled ) + { + fTargetCityAllowsRoaming = ( !gfMilitiaAllowedInTown[GetTownIdForSector( sMapX, sMapY )] ); + } + else + { + fTargetCityAllowsRoaming = FALSE; + } + } + else + { + fTargetCityAllowsRoaming = FALSE; + } + } + + //return (GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR || fWithCities) && !IsThisSectorASAMSector( sMapX, sMapY , 0 ) && + // (GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR || !fOnlyCitySectors);// && + return (fTargetCityAllowsRoaming || fWithCities) && !IsThisSectorASAMSector( sMapX, sMapY , 0 ) && (GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR || !fOnlyCitySectors);// && // ( !NumEnemiesInSector( sMapX, sMapY ) || GetEnemyGroupIdInSector(sMapX, sMapY ) || fForBattle ) && // ( fForBattle || CountAllMilitiaInSector( sMapX, sMapY ) < gGameExternalOptions.guiMaxMilitiaSquadSize || PlayerMercsInSector( sMapX, sMapY, 0 ) ) @@ -482,6 +678,7 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ) UINT8 x;//,y; SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); UINT8 uiMilitiaCount; + BOOLEAN fSourceCityAllowsRoaming; // If we don't want roaming militia @@ -521,9 +718,14 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ) } } + // HEADROCK HAM B2.7: If INI flag is set, allow militia to move out of Minor City sectors (Orta, Tixa, San Mona, etc). + if (gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities) + fSourceCityAllowsRoaming = ( !gfMilitiaAllowedInTown[GetTownIdForSector( sMapX, sMapY )] ); + else + fSourceCityAllowsRoaming = ( GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR ); // moving squad, if it is not a SAM site - if( ( GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR ) && (!IsThisSectorASAMSector( sMapX, sMapY, 0 )) ) + if( ( fSourceCityAllowsRoaming ) && (!IsThisSectorASAMSector( sMapX, sMapY, 0 )) ) { if( !PlayerMercsInSector_MSE( (UINT8)sMapX, (UINT8)sMapY, FALSE ) ) // and there's no player's mercs in the sector { @@ -559,13 +761,74 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ) //Kaiden: if Restricted Sectors List option is turned on, // militia can't move to any sectors in the list. // Unless they are following a group of mercs. + /////////////////////////////////////////////////////////////////// + // HEADROCK HAM B1: Restrict roamers by XML as normal, but also + // allow them to move through visited sectors. + // Also, there's an option now to use dynamic restriction. The + // AdjustRoamingRestrictions() function is used to dynamically + // define which sectors are restricted, based on city conquest. + /////////////////////////////////////////////////////////////////// + if (gGameExternalOptions.gflimitedRoaming) - for (INT32 i=0;i= uiDirNumber)iRandomRes = 0; - - //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld:Dir count %ld, Rand %ld. Go to %ld,%ld", sMapX, sMapY, uiDirNumber, iRandomRes, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] )); - - GenerateMilitiaSquad( sMapX, sMapY, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); - AddToBlockMoveList( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); - - if( NumEnemiesInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) && CountAllMilitiaInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) ) + for( x = 0; x < uiDirNumber; ++x) + if( iRandomRes < pMoveDir[x][1] ) { - /* GROUP* pEnemyGroup = GetGroup( GetEnemyGroupIdInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) ); - - if(pEnemyGroup && pEnemyGroup->ubGroupID) - { - //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Attacking from %ld,%ld to %ld,%ld - enemy's group id %ld", sMapX, sMapY, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0], pEnemyGroup->ubGroupID )); - //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Arrival 1, Arrival 2"); - - pEnemyGroup->ubPrevX = sMapX; - pEnemyGroup->ubPrevY = sMapY; - - pEnemyGroup->ubNextX = SECTORX( pMoveDir[ iRandomRes ][0] ); - pEnemyGroup->ubNextY = SECTORY( pMoveDir[ iRandomRes ][0] ); - */ - gfMSBattle = TRUE; - // GroupArrivedAtSector( pEnemyGroup->ubGroupID , TRUE, FALSE ); - - EnterAutoResolveMode( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); - // } + iRandomRes = x; + break; } + // shouldn't be! + if(iRandomRes >= uiDirNumber)iRandomRes = 0; + + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld:Dir count %ld, Rand %ld. Go to %ld,%ld", sMapX, sMapY, uiDirNumber, iRandomRes, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] )); + + GenerateMilitiaSquad( sMapX, sMapY, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); + fFoundValidSector = TRUE; + AddToBlockMoveList( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); + + if( NumEnemiesInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) && CountAllMilitiaInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) ) + { +/* GROUP* pEnemyGroup = GetGroup( GetEnemyGroupIdInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) ); + + if(pEnemyGroup && pEnemyGroup->ubGroupID) + { + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Attacking from %ld,%ld to %ld,%ld - enemy's group id %ld", sMapX, sMapY, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0], pEnemyGroup->ubGroupID )); + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Arrival 1, Arrival 2"); + + pEnemyGroup->ubPrevX = sMapX; + pEnemyGroup->ubPrevY = sMapY; + + pEnemyGroup->ubNextX = SECTORX( pMoveDir[ iRandomRes ][0] ); + pEnemyGroup->ubNextY = SECTORY( pMoveDir[ iRandomRes ][0] ); +*/ + gfMSBattle = TRUE; +// GroupArrivedAtSector( pEnemyGroup->ubGroupID , TRUE, FALSE ); + + EnterAutoResolveMode( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); +// } } - else return; + } + // HEADROCK HAM B2.7: Smarter check - looks at each city sector, and checks if there's room around that + // sector to place militia. Also relies on whether the sector has full militia before generating more. + // This section only runs if the program has failed to find suitable sectors around the "home" sector + // (where militia is being trained). + + iCounter = 0; + if (gGameExternalOptions.fSmartRoamingMilitiaGenerator) + { + while( pTownNamesList[ iCounter ] != 0 && fFoundValidSector == FALSE ) + { + if( pTownNamesList[ iCounter] == bTownId ) + { + sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + sCurrentY = GET_Y_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + + // if sector has enemies or hasn't already been taken at least once, then + if ( !SectorInfo[ SECTOR(sCurrentX, sCurrentY) ].fSurfaceWasEverPlayerControlled || NumEnemiesInSector( sCurrentX, sCurrentY ) > 0 ) + { + // skip the rest. Generate no militia from this sector. + iCounter++; + continue; + } + + GenerateDirectionInfosWithCapacityCheck( sCurrentX, sCurrentY, &uiDirNumber, pMoveDir, FALSE, TRUE, FALSE ); + + if(uiDirNumber)// && Random(100) < CHANCE_TO_GENERATE_A_SQUAD) + { + for( x = 1; x < uiDirNumber ; ++x )pMoveDir[x][1] += pMoveDir[x-1][1]; + + iRandomRes = Random( pMoveDir[ uiDirNumber - 1 ][1] ); + + for( x = 0; x < uiDirNumber; ++x) + if( iRandomRes < pMoveDir[x][1] ) + { + iRandomRes = x; + break; + } + + // shouldn't be! + if(iRandomRes >= uiDirNumber)iRandomRes = 0; + + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld:Dir count %ld, Rand %ld. Go to %ld,%ld", sMapX, sMapY, uiDirNumber, iRandomRes, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] )); + + GenerateMilitiaSquad( sCurrentX, sCurrentY, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); + fFoundValidSector = TRUE; + AddToBlockMoveList( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); + + if( NumEnemiesInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) && CountAllMilitiaInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) ) + { + /* GROUP* pEnemyGroup = GetGroup( GetEnemyGroupIdInSector( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ) ); + + if(pEnemyGroup && pEnemyGroup->ubGroupID) + { + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Attacking from %ld,%ld to %ld,%ld - enemy's group id %ld", sMapX, sMapY, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0], pEnemyGroup->ubGroupID )); + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Arrival 1, Arrival 2"); + + pEnemyGroup->ubPrevX = sMapX; + pEnemyGroup->ubPrevY = sMapY; + + pEnemyGroup->ubNextX = SECTORX( pMoveDir[ iRandomRes ][0] ); + pEnemyGroup->ubNextY = SECTORY( pMoveDir[ iRandomRes ][0] ); + */ + gfMSBattle = TRUE; + // GroupArrivedAtSector( pEnemyGroup->ubGroupID , TRUE, FALSE ); + + EnterAutoResolveMode( SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ) ); + // } + } + } + + } + iCounter++; + } + } + } return; } - - - - - - - - extern BOOLEAN gfMSResetMilitia; void DoMilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY ) @@ -837,3 +1176,359 @@ void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ) MoveMilitiaSquad( sMapX, sMapY, sDMapX, sDMapY, FALSE ); } +// HEADROCK (HAM): New function to alter Restricted Roaming based on the recent capture of new towns. The area +// around the town will be unrestricted, as well as some road sectors leading away from the town. +// This function runs during sector conquest checks, and only if an entire town has been conquered. It also +// runs at day end, as well as on load/save. +void AdjustRoamingRestrictions() +{ + if (!gGameExternalOptions.bDynamicRestrictRoaming) + return; + + // to do: Add something to clean up the entire array before setting the flags... + + if ( IsTownUnderCompleteControlByPlayer(DRASSEN) ) + { + // DRASSEN OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(12,1)] = true; + gDynamicRestrictMilitia[SECTOR(13,1)] = true; + gDynamicRestrictMilitia[SECTOR(14,1)] = true; + gDynamicRestrictMilitia[SECTOR(12,2)] = true; + gDynamicRestrictMilitia[SECTOR(14,2)] = true; + gDynamicRestrictMilitia[SECTOR(12,3)] = true; + gDynamicRestrictMilitia[SECTOR(14,3)] = true; + gDynamicRestrictMilitia[SECTOR(12,4)] = true; + gDynamicRestrictMilitia[SECTOR(14,4)] = true; + gDynamicRestrictMilitia[SECTOR(12,5)] = true; + gDynamicRestrictMilitia[SECTOR(13,5)] = true; + gDynamicRestrictMilitia[SECTOR(14,5)] = true; + + // SAM SITE DEFENSE + gDynamicRestrictMilitia[SECTOR(15,5)] = true; + + // ROAD WEST TO OMERTA + gDynamicRestrictMilitia[SECTOR(9,2)] = true; + gDynamicRestrictMilitia[SECTOR(10,2)] = true; + gDynamicRestrictMilitia[SECTOR(11,2)] = true; + + // ROAD SOUTH TO ALMA + gDynamicRestrictMilitia[SECTOR(12,6)] = true; + + } + + if ( IsTownUnderCompleteControlByPlayer(ALMA) ) + { + // ALMA OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(12,7)] = true; + gDynamicRestrictMilitia[SECTOR(13,7)] = true; + gDynamicRestrictMilitia[SECTOR(14,7)] = true; + gDynamicRestrictMilitia[SECTOR(15,7)] = true; + gDynamicRestrictMilitia[SECTOR(12,8)] = true; + gDynamicRestrictMilitia[SECTOR(15,8)] = true; + gDynamicRestrictMilitia[SECTOR(12,9)] = true; + gDynamicRestrictMilitia[SECTOR(15,9)] = true; + gDynamicRestrictMilitia[SECTOR(12,10)] = true; + gDynamicRestrictMilitia[SECTOR(13,10)] = true; + gDynamicRestrictMilitia[SECTOR(14,10)] = true; + gDynamicRestrictMilitia[SECTOR(15,10)] = true; + + // ROAD SOUTH + gDynamicRestrictMilitia[SECTOR(14,11)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CHITZENA) ) + { + // CHITZENA OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(1,1)] = true; + gDynamicRestrictMilitia[SECTOR(3,1)] = true; + gDynamicRestrictMilitia[SECTOR(1,2)] = true; + gDynamicRestrictMilitia[SECTOR(3,2)] = true; + gDynamicRestrictMilitia[SECTOR(1,3)] = true; + gDynamicRestrictMilitia[SECTOR(2,3)] = true; + gDynamicRestrictMilitia[SECTOR(3,3)] = true; + + // SAM SITE DEFENSE / ROAD SOUTH + gDynamicRestrictMilitia[SECTOR(3,4)] = true; + gDynamicRestrictMilitia[SECTOR(3,5)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) ) + { + // CAMBRIA OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(7,5)] = true; + gDynamicRestrictMilitia[SECTOR(8,5)] = true; + gDynamicRestrictMilitia[SECTOR(9,5)] = true; + gDynamicRestrictMilitia[SECTOR(10,5)] = true; + gDynamicRestrictMilitia[SECTOR(7,6)] = true; + gDynamicRestrictMilitia[SECTOR(10,6)] = true; + gDynamicRestrictMilitia[SECTOR(7,7)] = true; + gDynamicRestrictMilitia[SECTOR(10,7)] = true; + gDynamicRestrictMilitia[SECTOR(7,8)] = true; + gDynamicRestrictMilitia[SECTOR(9,8)] = true; + gDynamicRestrictMilitia[SECTOR(10,8)] = true; + + // SAM SITE DEFENSE + gDynamicRestrictMilitia[SECTOR(7,9)] = true; + gDynamicRestrictMilitia[SECTOR(9,9)] = true; + + // ROAD TO OMERTA + gDynamicRestrictMilitia[SECTOR(9,2)] = true; + gDynamicRestrictMilitia[SECTOR(9,3)] = true; + gDynamicRestrictMilitia[SECTOR(9,4)] = true; + + // CROSSROADS WEST + gDynamicRestrictMilitia[SECTOR(6,7)] = true; + + // ROAD TO ALMA + gDynamicRestrictMilitia[SECTOR(11,7)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(BALIME) ) + { + // BALIME OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(10,11)] = true; + gDynamicRestrictMilitia[SECTOR(11,11)] = true; + gDynamicRestrictMilitia[SECTOR(12,11)] = true; + gDynamicRestrictMilitia[SECTOR(13,11)] = true; + gDynamicRestrictMilitia[SECTOR(10,12)] = true; + gDynamicRestrictMilitia[SECTOR(13,12)] = true; + gDynamicRestrictMilitia[SECTOR(10,13)] = true; + gDynamicRestrictMilitia[SECTOR(13,13)] = true; + + // WEST ROAD + gDynamicRestrictMilitia[SECTOR(6,11)] = true; + gDynamicRestrictMilitia[SECTOR(7,11)] = true; + gDynamicRestrictMilitia[SECTOR(8,11)] = true; + gDynamicRestrictMilitia[SECTOR(9,11)] = true; + + // SOUTH ROAD + gDynamicRestrictMilitia[SECTOR(8,14)] = true; + gDynamicRestrictMilitia[SECTOR(9,14)] = true; + gDynamicRestrictMilitia[SECTOR(10,14)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(GRUMM) ) + { + // GRUMM OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(1,6)] = true; + gDynamicRestrictMilitia[SECTOR(2,6)] = true; + gDynamicRestrictMilitia[SECTOR(3,6)] = true; + gDynamicRestrictMilitia[SECTOR(3,7)] = true; + gDynamicRestrictMilitia[SECTOR(4,7)] = true; + gDynamicRestrictMilitia[SECTOR(4,8)] = true; + gDynamicRestrictMilitia[SECTOR(3,9)] = true; + gDynamicRestrictMilitia[SECTOR(4,9)] = true; + + // ROAD NORTH + gDynamicRestrictMilitia[SECTOR(3,5)] = true; + gDynamicRestrictMilitia[SECTOR(3,4)] = true; + + // ROAD EAST + gDynamicRestrictMilitia[SECTOR(5,7)] = true; + gDynamicRestrictMilitia[SECTOR(6,7)] = true; + + // ROAD SOUTH + gDynamicRestrictMilitia[SECTOR(2,10)] = true; + gDynamicRestrictMilitia[SECTOR(3,10)] = true; + gDynamicRestrictMilitia[SECTOR(2,11)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(DRASSEN) && IsTownUnderCompleteControlByPlayer(CHITZENA) ) + { + // ROAD EAST OF SAN-MONA + gDynamicRestrictMilitia[SECTOR(7,3)] = true; + gDynamicRestrictMilitia[SECTOR(8,3)] = true; + gDynamicRestrictMilitia[SECTOR(9,3)] = true; + + // SAN MONA + gDynamicRestrictMilitia[SECTOR(5,3)] = true; + gDynamicRestrictMilitia[SECTOR(6,3)] = true; + gDynamicRestrictMilitia[SECTOR(4,4)] = true; + gDynamicRestrictMilitia[SECTOR(5,4)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) && ( IsTownUnderCompleteControlByPlayer(CHITZENA) || IsTownUnderCompleteControlByPlayer(DRASSEN) ) ) + { + // ROAD TO SAN MONA + gDynamicRestrictMilitia[SECTOR(7,3)] = true; + gDynamicRestrictMilitia[SECTOR(8,3)] = true; + gDynamicRestrictMilitia[SECTOR(9,3)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) && ( IsTownUnderCompleteControlByPlayer(DRASSEN) || IsTownUnderCompleteControlByPlayer(CHITZENA) || IsTownUnderCompleteControlByPlayer(GRUMM) ) ) + { + // ROAD SOUTH-EAST OF SAN MONA + gDynamicRestrictMilitia[SECTOR(6,4)] = true; + gDynamicRestrictMilitia[SECTOR(7,4)] = true; + + // SAN MONA + gDynamicRestrictMilitia[SECTOR(5,3)] = true; + gDynamicRestrictMilitia[SECTOR(6,3)] = true; + gDynamicRestrictMilitia[SECTOR(4,4)] = true; + gDynamicRestrictMilitia[SECTOR(5,4)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(ALMA) && ( IsTownUnderCompleteControlByPlayer(DRASSEN) || IsTownUnderCompleteControlByPlayer(CAMBRIA) ) ) + { + // ROAD WEST OF ALMA + gDynamicRestrictMilitia[SECTOR(11,7)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CHITZENA) && ( IsTownUnderCompleteControlByPlayer(DRASSEN) || IsTownUnderCompleteControlByPlayer(CAMBRIA) || IsTownUnderCompleteControlByPlayer(GRUMM) ) ) + { + // SAN MONA + gDynamicRestrictMilitia[SECTOR(5,3)] = true; + gDynamicRestrictMilitia[SECTOR(6,3)] = true; + gDynamicRestrictMilitia[SECTOR(4,4)] = true; + gDynamicRestrictMilitia[SECTOR(5,4)] = true; + + // ROAD TO SAN MONA + gDynamicRestrictMilitia[SECTOR(7,3)] = true; + gDynamicRestrictMilitia[SECTOR(8,3)] = true; + gDynamicRestrictMilitia[SECTOR(9,3)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) && IsTownUnderCompleteControlByPlayer(ALMA) ) + { + // ROAD EAST OF CAMBRIA + gDynamicRestrictMilitia[SECTOR(11,7)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) && ( IsTownUnderCompleteControlByPlayer(BALIME) || IsTownUnderCompleteControlByPlayer(GRUMM) || IsTownUnderCompleteControlByPlayer(ALMA) ) ) + { + // UPGRADED SAM-SITE DEFENSE FOR CAMBRIA + gDynamicRestrictMilitia[SECTOR(7,10)] = true; + gDynamicRestrictMilitia[SECTOR(8,10)] = true; + gDynamicRestrictMilitia[SECTOR(9,10)] = true; // Tixa. Won't be accessible unless Minor Cities are allowed. + } + + if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) && IsTownUnderCompleteControlByPlayer(GRUMM) ) + { + // ESTONI + gDynamicRestrictMilitia[SECTOR(6,8)] = true; + + // ESTONI MOUNTAIN GATE + gDynamicRestrictMilitia[SECTOR(6,9)] = true; + + // SOUTHEAST ROAD TO SAN MONA + gDynamicRestrictMilitia[SECTOR(6,4)] = true; + gDynamicRestrictMilitia[SECTOR(7,4)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(BALIME) && IsTownUnderCompleteControlByPlayer(GRUMM) && IsTownUnderCompleteControlByPlayer(CAMBRIA) ) + { + // GRUMM-CAMBRIA-BALIME ROAD + gDynamicRestrictMilitia[SECTOR(6,10)] = true; + } + + // CONTROL ALL TOWNS? + if ( IsTownUnderCompleteControlByPlayer(BALIME) && IsTownUnderCompleteControlByPlayer(CAMBRIA) && IsTownUnderCompleteControlByPlayer(CHITZENA) && IsTownUnderCompleteControlByPlayer(DRASSEN) && IsTownUnderCompleteControlByPlayer(ALMA) && IsTownUnderCompleteControlByPlayer(GRUMM) ) + { + // ROADS TO MEDUNA + gDynamicRestrictMilitia[SECTOR(6,12)] = true; + gDynamicRestrictMilitia[SECTOR(7,14)] = true; + gDynamicRestrictMilitia[SECTOR(2,12)] = true; + + // MEDUNA OUTSKIRTS + gDynamicRestrictMilitia[SECTOR(2,13)] = true; + gDynamicRestrictMilitia[SECTOR(3,13)] = true; + gDynamicRestrictMilitia[SECTOR(4,13)] = true; + gDynamicRestrictMilitia[SECTOR(5,13)] = true; + gDynamicRestrictMilitia[SECTOR(6,13)] = true; + gDynamicRestrictMilitia[SECTOR(6,14)] = true; + } +} + +// HEADROCK HAM B2.7: This is a copy of an existing function that generates possible movement directions for militia. +// Due to the widespread use of that function, I've decided to make a new one that calculates directions +// when generating militia. This function checks for available space at the target location. It won't count green +// or regular militia in this total, as they will be replaced by higher grade militia when added. + +void GenerateDirectionInfosWithCapacityCheck( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ) +{ + SECTORINFO *pSectorInfo; + + *uiDirNumber = 0; + +#ifdef DEBUG_SHOW_RATINGS + if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld-------------------", sMapX, sMapY ); +#endif + + pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY - 1 ) ] ); + if(CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY - 1, fWithCities, fForBattle, fOnlyCitySectors ) && + pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < gGameExternalOptions.iMaxMilitiaPerSector ) + { + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY - 1 ); + + if( fForBattle ) + pMoveDir[ *uiDirNumber ][1] = 0; + else + pMoveDir[ *uiDirNumber ][1] = CountDirectionRating( sMapX, sMapY, DIR_NORTH ); + + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_NORTH; + +#ifdef DEBUG_SHOW_RATINGS + if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Priority of north direction - %ld", pMoveDir[ *uiDirNumber ][1]); +#endif + + ++(*uiDirNumber); + } + pSectorInfo = &( SectorInfo[ SECTOR( sMapX - 1, sMapY ) ] ); + if(CheckStandardConditionsForDirection( sMapX, sMapY, sMapX - 1, sMapY, fWithCities, fForBattle, fOnlyCitySectors ) && + pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < gGameExternalOptions.iMaxMilitiaPerSector) + { + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX - 1, sMapY ); + + if( fForBattle ) + pMoveDir[ *uiDirNumber ][1] = 0; + else + pMoveDir[ *uiDirNumber ][1] = CountDirectionRating( sMapX, sMapY, DIR_WEST ); + + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_WEST; + +#ifdef DEBUG_SHOW_RATINGS + if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Priority of west direction - %ld", pMoveDir[ *uiDirNumber ][1]); +#endif + + ++(*uiDirNumber); + } + pSectorInfo = &( SectorInfo[ SECTOR( sMapX , sMapY + 1 ) ] ); + if(CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY + 1, fWithCities, fForBattle, fOnlyCitySectors ) && + pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < gGameExternalOptions.iMaxMilitiaPerSector ) + { + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY + 1 ); + + if( fForBattle ) + pMoveDir[ *uiDirNumber ][1] = 0; + else + pMoveDir[ *uiDirNumber ][1] = CountDirectionRating( sMapX, sMapY, DIR_SOUTH ); + + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_SOUTH; + +#ifdef DEBUG_SHOW_RATINGS + if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Priority of south direction - %ld", pMoveDir[ *uiDirNumber ][1]); +#endif + + ++(*uiDirNumber); + } + pSectorInfo = &( SectorInfo[ SECTOR( sMapX + 1 , sMapY ) ] ); + if(CheckStandardConditionsForDirection( sMapX, sMapY, sMapX + 1, sMapY, fWithCities, fForBattle, fOnlyCitySectors ) && + pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < gGameExternalOptions.iMaxMilitiaPerSector ) + { + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX + 1, sMapY ); + + if( fForBattle ) + pMoveDir[ *uiDirNumber ][1] = 0; + else + pMoveDir[ *uiDirNumber ][1] = CountDirectionRating( sMapX, sMapY, DIR_EAST ); + + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_EAST; + +#ifdef DEBUG_SHOW_RATINGS + if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Priority of east direction - %ld", pMoveDir[ *uiDirNumber ][1]); +#endif + + ++(*uiDirNumber); + } + +} diff --git a/Strategic/MilitiaSquads.h b/Strategic/MilitiaSquads.h index fb8f13cf..7feedaba 100644 --- a/Strategic/MilitiaSquads.h +++ b/Strategic/MilitiaSquads.h @@ -22,4 +22,16 @@ void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT1 BOOLEAN MoveOneBestMilitiaMan(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY); void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ); +// HEADROCK HAM B1: Changes the allowed militia sectors +extern void AdjustRoamingRestrictions(); +// HEADROCK HAM B1: Alternate array keeps track of dynamically unrestricted sectors +extern BOOLEAN gDynamicRestrictMilitia[ 256 ]; +// HEADROCK HAM B2.7: Wonder why this function wasn't declared here. It is now, to allow Town Militia.cpp to +// access it. + +BOOLEAN CheckStandardConditionsForDirection( INT16 sSMapX, INT16 sSMapY, INT16 sMapX, INT16 sMapY, BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ); + +// HEADROCK HAM B2.7: Generates possible movement directions - this is used only in CreateMilitiaGroup(), and +// makes sure that the target sector has room for more militia. +void GenerateDirectionInfosWithCapacityCheck( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ); #endif \ No newline at end of file diff --git a/Strategic/Strategic Merc Handler.cpp b/Strategic/Strategic Merc Handler.cpp index 6a270a1e..8f040ff6 100644 --- a/Strategic/Strategic Merc Handler.cpp +++ b/Strategic/Strategic Merc Handler.cpp @@ -40,6 +40,8 @@ #include "finances.h" #include "GameSettings.h" #include "Quests.h" + // HEADROCK HAM B1: Additional Include for Dynamic Roaming Restrictions + #include "MilitiaSquads.h" #endif @@ -510,6 +512,11 @@ void MercDailyUpdate() // rebuild list for mapscreen ReBuildCharactersList( ); + // HEADROCK HAM B1: Run a function to redefine Roaming Militia Restrictions. + if (gGameExternalOptions.bDynamicRestrictRoaming) + { + AdjustRoamingRestrictions(); + } } /* diff --git a/Strategic/Strategic Mines.cpp b/Strategic/Strategic Mines.cpp index 0d8cacad..f474f22b 100644 --- a/Strategic/Strategic Mines.cpp +++ b/Strategic/Strategic Mines.cpp @@ -342,7 +342,8 @@ UINT32 GetMaxPeriodicRemovalFromMine( INT8 bMineIndex ) return ( 0 ); } - return( gMineStatus[ bMineIndex ].uiMaxRemovalRate ); + // HEADROCK HAM B1: Affected by external INI option. + return(( gMineStatus[ bMineIndex ].uiMaxRemovalRate * gGameExternalOptions.iMineIncomePercentage ) / 100); } @@ -368,6 +369,8 @@ UINT32 GetMaxDailyRemovalFromMine( INT8 bMineIndex ) // yes, reduce to value of mine uiAmtExtracted = gMineStatus[ bMineIndex ].uiRemainingOreSupply; } + // HEADROCK HAM B1: Affected by external INI option. + uiAmtExtracted = (uiAmtExtracted * gGameExternalOptions.iMineIncomePercentage) / 100; return(uiAmtExtracted); } @@ -664,6 +667,8 @@ void HandleIncomeFromMines( void ) // mine this mine iIncome += MineAMine( bCounter ); } + // HEADROCK HAM B1: Affected by external INI Option. + iIncome = (iIncome * gGameExternalOptions.iMineIncomePercentage) / 100; if( iIncome ) { AddTransactionToPlayersBook( DEPOSIT_FROM_SILVER_MINE, 0, GetWorldTotalMin( ), iIncome ); @@ -690,7 +695,8 @@ UINT32 PredictDailyIncomeFromAMine( INT8 bMineIndex ) } } - return( uiAmtExtracted ); + // HEADROCK HAM B1: Affected by external INI Option. + return( ( uiAmtExtracted * gGameExternalOptions.iMineIncomePercentage ) / 100 ) ; } @@ -720,6 +726,8 @@ INT32 CalcMaxPlayerIncomeFromMines( void ) // add up the total iTotal += (MINE_PRODUCTION_NUMBER_OF_PERIODS * gMineStatus[bCounter].uiMaxRemovalRate); } + // HEADROCK HAM B1: Affected by external INI Option. + iTotal = (iTotal * gGameExternalOptions.iMineIncomePercentage) / 100; return( iTotal ); } diff --git a/Strategic/Strategic Town Loyalty.cpp b/Strategic/Strategic Town Loyalty.cpp index 898d8f5d..ede8e7af 100644 --- a/Strategic/Strategic Town Loyalty.cpp +++ b/Strategic/Strategic Town Loyalty.cpp @@ -33,6 +33,8 @@ #include "history.h" #include "meanwhile.h" #include "Strategic Status.h" + // HEADROCK HAM B1: Added include for Dynamic Roaming Militia + #include "MilitiaSquads.h" #endif // the max loyalty rating for any given town @@ -1814,6 +1816,11 @@ void CheckIfEntireTownHasBeenLiberated( INT8 bTownId, INT16 sSectorX, INT16 sSec { UpdateLastDayOfPlayerActivity( ( UINT16 ) ( GetWorldDay() + 2 ) ); } + // HEADROCK HAM B1: Run a function to redefine Roaming Militia Restrictions on city capture. + if (gGameExternalOptions.bDynamicRestrictRoaming) + { + AdjustRoamingRestrictions(); + } // set flag even for towns where you can't train militia, useful for knowing Orta/Tixa were previously controlled gTownLoyalty[ bTownId ].fLiberatedAlready = TRUE; diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index a619022f..04d385e5 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -653,6 +653,8 @@ extern BOOLEAN gfDeductPoints; extern void CleanUpStack( OBJECTTYPE * pObj, OBJECTTYPE * pCursorObj ); extern void SwapGoggles(SOLDIERTYPE *pTeamSoldier); +// HEADROCK HAM B2.8: Function to switch team's goggles uniformly +extern void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision); UINT32 guiCHARLIST; UINT32 guiCHARINFO; @@ -1038,7 +1040,8 @@ void CheckForInventoryModeCancellation(); void ChangeMapScreenMaskCursor( UINT16 usCursor ); void CancelOrShortenPlottedPath( void ); -BOOLEAN HandleCtrlOrShiftInTeamPanel( INT8 bCharNumber ); +// HEADROCK HAM B2.8: Added argument to enable multi-selecting entire squads +BOOLEAN HandleCtrlOrShiftInTeamPanel( INT8 bCharNumber, BOOLEAN fFromRightClickAssignments ); INT32 GetContractExpiryTime( SOLDIERTYPE *pSoldier ); @@ -9674,7 +9677,8 @@ void TeamListInfoRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) if( gCharactersList[ iValue ].fValid == TRUE ) { - if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue ) ) + // HEADROCK HAM B2.8: Added argument for multi-select entire squads + if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue , FALSE )) { return; } @@ -9824,7 +9828,8 @@ void TeamListAssignmentRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) if( gCharactersList[ iValue ].fValid == TRUE ) { - if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue ) ) + // HEADROCK HAM B2.8: Added argument for multi-select entire squads + if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue , FALSE )) { return; } @@ -9892,8 +9897,87 @@ void TeamListAssignmentRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) if (iReason & MSYS_CALLBACK_REASON_RBUTTON_UP) { + // HEADROCK HAM B2.8: Added argument for multi-select entire squads + if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue, TRUE )) + { + return; + } // reset selected characters ResetAllSelectedCharacterModes( ); + // HEADROCK HAM B2.8: Right mouse button selects entire squad, if clicked on a squad-assigned character. + // set to new info character...make sure is valid + if( IsMapScreenHelpTextUp() ) + { + // stop mapscreen text + StopMapScreenHelpText( ); + return; + } + + if( gCharactersList[ iValue ].fValid == TRUE ) + { + + ChangeSelectedInfoChar( ( INT8 ) iValue, TRUE ); + + pSoldier = &Menptr[ gCharactersList[ iValue ].usSolID ]; + + // highlight + giDestHighLine = -1; + + // reset character + bSelectedAssignChar = -1; + bSelectedDestChar = -1; + bSelectedContractChar = -1; + fPlotForHelicopter = FALSE; + + // if not dead or POW, select his sector + if( ( pSoldier->stats.bLife > 0 ) && ( pSoldier->bAssignment != ASSIGNMENT_POW ) ) + { + ChangeSelectedMapSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); + } + + // Select all characters in squad + INT16 iCounter; + + for( iCounter = 0; iCounter < CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS; iCounter++ ) + { + if( gCharactersList[ iCounter ].fValid == TRUE ) + { + // if not already selected + if( fSelectedListOfMercsForMapScreen[ iCounter ] == FALSE ) + { + pSoldier = &( Menptr[ gCharactersList[ iCounter ].usSolID ] ); + + // if on a squad or in a vehicle + if ( ( pSoldier->bAssignment < ON_DUTY ) || ( pSoldier->bAssignment == VEHICLE ) ) + { + // and a member of that squad or vehicle is selected + if ( AnyMercInSameSquadOrVehicleIsSelected( pSoldier ) ) + { + // then also select this guy + SetEntryInSelectedCharacterList( ( INT8 ) iCounter ); + } + } + } + } + } + + // unhilight contract line + giContractHighLine = -1; + + // can't assign highlight line + giAssignHighLine = -1; + + // dirty team and map regions + fTeamPanelDirty = TRUE; + fMapPanelDirty = TRUE; + //fMapScreenBottomDirty = TRUE; + gfRenderPBInterface = TRUE; + } + else + { + // reset selected characters + ResetAllSelectedCharacterModes( ); + } } } @@ -9980,7 +10064,8 @@ void TeamListDestinationRegionBtnCallBack(MOUSE_REGION *pRegion, INT32 iReason ) if( gCharactersList[ iValue ].fValid == TRUE ) { - if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue ) ) + // HEADROCK HAM B2.8: Added argument for multi-select entire squads + if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue , FALSE )) { return; } @@ -10153,7 +10238,8 @@ void TeamListSleepRegionBtnCallBack( MOUSE_REGION *pRegion, INT32 iReason ) if( ( gCharactersList[ iValue ].fValid == TRUE ) ) { - if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue ) ) + // HEADROCK HAM B2.8: Added argument for multi-select entire squads + if ( HandleCtrlOrShiftInTeamPanel( ( INT8 ) iValue , FALSE )) { return; } @@ -10171,6 +10257,8 @@ void TeamListSleepRegionBtnCallBack( MOUSE_REGION *pRegion, INT32 iReason ) // try to wake him up if( SetMercAwake( pSoldier, TRUE, FALSE ) ) { + // HEADROCK HAM B2.8: New feature wakes all trainees/trainers automatically + HandleTrainingWakeSynchronize( pSoldier ); // propagate HandleSelectedMercsBeingPutAsleep( TRUE, TRUE ); return; @@ -10185,6 +10273,8 @@ void TeamListSleepRegionBtnCallBack( MOUSE_REGION *pRegion, INT32 iReason ) // try to put him to sleep if( SetMercAsleep( pSoldier, TRUE ) ) { + // HEADROCK HAM B2.8: New feature sends all trainees/trainers to sleep automatically + HandleTrainingSleepSynchronize( pSoldier ); // propagate HandleSelectedMercsBeingPutAsleep( FALSE, TRUE ); return; @@ -13853,34 +13943,160 @@ void CancelOrShortenPlottedPath( void ) } -BOOLEAN HandleCtrlOrShiftInTeamPanel( INT8 bCharNumber ) +BOOLEAN HandleCtrlOrShiftInTeamPanel( INT8 bCharNumber, BOOLEAN fFromRightClickAssignments ) { - // check if shift or ctrl held down, if so, set values in list - if (_KeyDown(CTRL)) + // HEADROCK HAM B2.8: New condition based on new argument. + if (fFromRightClickAssignments) { - ToggleEntryInSelectedList( bCharNumber ); + if ( _KeyDown(CTRL) || _KeyDown(SHIFT) ) + { + if ( fSelectedListOfMercsForMapScreen[ bCharNumber ] == FALSE ) + { + SetEntryInSelectedCharacterList( bCharNumber ); - fTeamPanelDirty = TRUE; - fCharacterInfoPanelDirty = TRUE; + INT16 iCounter; - return( TRUE); + for( iCounter = 0; iCounter < CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS; iCounter++ ) + { + if( gCharactersList[ iCounter ].fValid == TRUE ) + { + // if not already selected + if( fSelectedListOfMercsForMapScreen[ iCounter ] == FALSE ) + { + SOLDIERTYPE * pSelected = &( Menptr[ gCharactersList[ bCharNumber ].usSolID ] ); + SOLDIERTYPE * pSoldier = &( Menptr[ gCharactersList[ iCounter ].usSolID ] ); + + // if on a squad, or in a vehicle, or IS a vehicle + if ( pSoldier->bAssignment == VEHICLE ) + { + // and a member of that squad or vehicle is selected, or we are a vehicle + if ( pSoldier->iVehicleId == pSelected->iVehicleId || + (pSelected->flags.uiStatusFlags & SOLDIER_VEHICLE && pSoldier->iVehicleId == pSelected->bVehicleID ) || + (pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE && pSelected->iVehicleId == pSoldier->bVehicleID ) ) + { + // then also select this guy + SetEntryInSelectedCharacterList( ( INT8 ) iCounter ); + } + } + + else if ( pSoldier->bAssignment == pSelected->bAssignment && + (pSoldier->bAssignment == TRAIN_TEAMMATE || pSoldier->bAssignment == TRAIN_BY_OTHER) && + pSoldier->sSectorX == pSelected->sSectorX && + pSoldier->sSectorY == pSelected->sSectorY && + pSoldier->sZLevel == pSelected->sZLevel ) + { + // make sure only trainers/trainees of the same stat are selected together. + if (pSoldier->bTrainStat == pSelected->bTrainStat) + { + SetEntryInSelectedCharacterList( ( INT8 ) iCounter ); + } + } + + else if ( pSoldier->bAssignment == pSelected->bAssignment && + pSoldier->sSectorX == pSelected->sSectorX && + pSoldier->sSectorY == pSelected->sSectorY && + pSoldier->sZLevel == pSelected->sZLevel ) + { + SetEntryInSelectedCharacterList( ( INT8 ) iCounter ); + } + } + } + } + } + + else + { + // Deselect entire group + ResetEntryForSelectedList( bCharNumber ); + + INT16 iCounter; + + for( iCounter = 0; iCounter < CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS; iCounter++ ) + { + if( gCharactersList[ iCounter ].fValid == TRUE ) + { + // if not already selected + if( fSelectedListOfMercsForMapScreen[ iCounter ] == TRUE ) + { + SOLDIERTYPE * pSelected = &( Menptr[ gCharactersList[ bCharNumber ].usSolID ] ); + SOLDIERTYPE * pSoldier = &( Menptr[ gCharactersList[ iCounter ].usSolID ] ); + + // if on a squad, or in a vehicle, or IS a vehicle + if ( pSoldier->bAssignment == VEHICLE ) + { + // and a member of that squad or vehicle is selected, or we are a vehicle + if ( pSoldier->iVehicleId == pSelected->iVehicleId || + (pSelected->flags.uiStatusFlags & SOLDIER_VEHICLE && pSoldier->iVehicleId == pSelected->bVehicleID ) || + (pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE && pSelected->iVehicleId == pSoldier->bVehicleID ) ) + { + // then also select this guy + ResetEntryForSelectedList( ( INT8 ) iCounter ); + } + } + + else if ( pSoldier->bAssignment == pSelected->bAssignment && + (pSoldier->bAssignment == TRAIN_TEAMMATE || pSoldier->bAssignment == TRAIN_BY_OTHER) && + pSoldier->sSectorX == pSelected->sSectorX && + pSoldier->sSectorY == pSelected->sSectorY && + pSoldier->sZLevel == pSelected->sZLevel ) + { + // make sure only trainers/trainees of the same stat are selected together. + if (pSoldier->bTrainStat == pSelected->bTrainStat) + { + ResetEntryForSelectedList( ( INT8 ) iCounter ); + } + } + + else if ( pSoldier->bAssignment == pSelected->bAssignment && + pSoldier->sSectorX == pSelected->sSectorX && + pSoldier->sSectorY == pSelected->sSectorY && + pSoldier->sZLevel == pSelected->sZLevel ) + { + ResetEntryForSelectedList( ( INT8 ) iCounter ); + } + } + } + } + } + + fTeamPanelDirty = TRUE; + fCharacterInfoPanelDirty = TRUE; + + return(TRUE); + } + + return (FALSE); } - else if( _KeyDown(SHIFT) ) + else { - // build a list from the bSelectedInfoChar To here, reset everyone - //empty the list - ResetSelectedListForMapScreen( ); - // rebuild the list - BuildSelectedListFromAToB( bSelectedInfoChar, bCharNumber ); + // check if shift or ctrl held down, if so, set values in list + if (_KeyDown(CTRL)) + { + ToggleEntryInSelectedList( bCharNumber ); - fTeamPanelDirty = TRUE; - fCharacterInfoPanelDirty = TRUE; + fTeamPanelDirty = TRUE; + fCharacterInfoPanelDirty = TRUE; - return( TRUE ); + return( TRUE); + } + else if( _KeyDown(SHIFT) ) + { + // build a list from the bSelectedInfoChar To here, reset everyone + + //empty the list + ResetSelectedListForMapScreen( ); + // rebuild the list + BuildSelectedListFromAToB( bSelectedInfoChar, bCharNumber ); + + fTeamPanelDirty = TRUE; + fCharacterInfoPanelDirty = TRUE; + + return( TRUE ); + } + + return( FALSE ); } - - return( FALSE ); } diff --git a/Strategic/mapscreen.h b/Strategic/mapscreen.h index e10a43d3..ab4e56d9 100644 --- a/Strategic/mapscreen.h +++ b/Strategic/mapscreen.h @@ -13,6 +13,9 @@ #define MAX_TOWNS 50 //13 //50 #define MAX_TOWN_SECTORS 260 //40 //260 +// HEADROCK HAM B2.7: Moved here from Strategicmap.cpp to allow use by MilitiaSquads.cpp + +extern BOOLEAN gfMilitiaAllowedInTown [ MAX_TOWNS ]; // Sector name identifiers enum Towns { diff --git a/Tactical/Bullets.h b/Tactical/Bullets.h index 0a9c3954..f20238fe 100644 --- a/Tactical/Bullets.h +++ b/Tactical/Bullets.h @@ -69,6 +69,10 @@ typedef struct UINT16 ubItemStatus; UINT16 fromItem; INT32 flash; + // HEADROCK HAM B2.5: Experimental bullet tracer info. If the new tracer system is activated, this flag tells + // the bullet animation functions to create a lightpath for this bullet. This means now all bullets in a tracer + // magazine will cause a lightshow (that's the intended result). + BOOLEAN fTracer; } BULLET; extern UINT32 guiNumBullets; diff --git a/Tactical/Campaign.cpp b/Tactical/Campaign.cpp index 217e5a1a..83224bfd 100644 --- a/Tactical/Campaign.cpp +++ b/Tactical/Campaign.cpp @@ -73,7 +73,11 @@ STR16 wDebugStatStrings[]={ // local prototypes UINT8 CalcImportantSectorControl( void ); +// HEADROCK HAM B1: New function to calculate how many important sectors there are in the game at all. +UINT8 CalcTotalImportantSectors( void ); UINT16 CountSurfaceSectorsVisited( void ); +// HAEDROCK HAM B1: New function to calculate how many sectors CAN be visited, on the surface. +UINT16 TotalVisitableSurfaceSectors( void ); @@ -1076,30 +1080,58 @@ UINT16 SubpointsPerPoint(UINT8 ubStat, INT8 bExpLevel) { UINT16 usSubpointsPerPoint; + UINT16 HEALTH_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubHealthSubpointsToImprove; + UINT16 STRENGTH_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubStrengthSubpointsToImprove; + UINT16 DEXTERITY_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubDexteritySubpointsToImprove; + UINT16 AGILITY_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubAgilitySubpointsToImprove; + UINT16 WISDOM_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubWisdomSubpointsToImprove; + UINT16 MARKSMANSHIP_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubMarksmanshipSubpointsToImprove; + UINT16 MEDICAL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubMedicalSubpointsToImprove; + UINT16 MECHANICAL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubMechanicalSubpointsToImprove; + UINT16 LEADERSHIP_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubLeadershipSubpointsToImprove; + UINT16 EXPLOSIVES_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubExplosivesSubpointsToImprove; + UINT16 LEVEL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.ubLevelSubpointsToImprove; // figure out how many subpoints this type of stat needs to change switch (ubStat) { + // Attributes case HEALTHAMT: + usSubpointsPerPoint = HEALTH_SUBPOINTS_TO_IMPROVE; + break; case AGILAMT: + usSubpointsPerPoint = AGILITY_SUBPOINTS_TO_IMPROVE; + break; case DEXTAMT: + usSubpointsPerPoint = DEXTERITY_SUBPOINTS_TO_IMPROVE; + break; case WISDOMAMT: - case STRAMT: - // attributes - usSubpointsPerPoint = ATTRIBS_SUBPOINTS_TO_IMPROVE; - break; - + usSubpointsPerPoint = WISDOM_SUBPOINTS_TO_IMPROVE; + break; + case STRAMT: + usSubpointsPerPoint = STRENGTH_SUBPOINTS_TO_IMPROVE; + break; + + // Skills case MEDICALAMT: + usSubpointsPerPoint = MEDICAL_SUBPOINTS_TO_IMPROVE; + break; case EXPLODEAMT: + usSubpointsPerPoint = EXPLOSIVES_SUBPOINTS_TO_IMPROVE; + break; case MECHANAMT: + usSubpointsPerPoint = MECHANICAL_SUBPOINTS_TO_IMPROVE; + break; case MARKAMT: - case LDRAMT: - // skills - usSubpointsPerPoint = SKILLS_SUBPOINTS_TO_IMPROVE; - break; + usSubpointsPerPoint = MARKSMANSHIP_SUBPOINTS_TO_IMPROVE; + break; + case LDRAMT: + usSubpointsPerPoint = LEADERSHIP_SUBPOINTS_TO_IMPROVE; + break; + // Experience case EXPERAMT: - usSubpointsPerPoint = LEVEL_SUBPOINTS_TO_IMPROVE * bExpLevel; - break; + usSubpointsPerPoint = LEVEL_SUBPOINTS_TO_IMPROVE * bExpLevel; + break; default: // BETA message @@ -1267,7 +1299,7 @@ UINT8 CurrentPlayerProgressPercentage(void) { UINT32 uiCurrentIncome; UINT32 uiPossibleIncome; - UINT8 ubCurrentProgress = gGameExternalOptions.ubGameProgressIncrement; + UINT8 ubCurrentProgress; UINT8 ubKillsPerPoint; UINT16 usKillsProgress; UINT16 usControlProgress; @@ -1332,7 +1364,12 @@ UINT8 CurrentPlayerProgressPercentage(void) // 19 sectors in mining towns + 3 wilderness SAMs each count double. Balime & Meduna are extra and not required - usControlProgress = CalcImportantSectorControl(); + // HEADROCK HAM B1: Changed the next line, adding a call to a new function. This allows the weight of Sector + // Control to be altered in JA2_OPTIONS.INI (Previously damaged the game's progress if set over 25... So I've + // made this MOD-Friendly :D ) + // BTW, Balime and Meduna _ARE_ required. The function doesn't differentiate! Such carelessness. Tsk tsk tsk. + + usControlProgress = gGameExternalOptions.ubGameProgressPortionControl * CalcImportantSectorControl() / CalcTotalImportantSectors(); if (usControlProgress > gGameExternalOptions.ubGameProgressPortionControl) { usControlProgress = gGameExternalOptions.ubGameProgressPortionControl; @@ -1343,21 +1380,14 @@ UINT8 CurrentPlayerProgressPercentage(void) // WDS: Adding more ways to progress in the game // Get a ratio of sectors visited to the total number of sectors - usVisitProgress = CountSurfaceSectorsVisited() * gGameExternalOptions.ubGameProgressPortionVisited / ((MAP_WORLD_X - 2) * (MAP_WORLD_Y - 2) - 56 /* note: should be calculated */); + // HEADROCK HAM B1: Fixed this so it doesn't count sectors that can't be visited. Allows progress to go to + // 100 even if the map has some unvisitable sectors (heh, doesn't it always?) + usVisitProgress = CountSurfaceSectorsVisited() * gGameExternalOptions.ubGameProgressPortionVisited / TotalVisitableSurfaceSectors(); - // add visit progress + // add control progress ubCurrentProgress += usVisitProgress; - // Add increment to progress - ubCurrentProgress += gGameExternalOptions.ubGameProgressIncrement; - - if (ubCurrentProgress < gGameExternalOptions.ubGameProgressMinimum) - return gGameExternalOptions.ubGameProgressMinimum; - - if (ubCurrentProgress < 100) - return ubCurrentProgress; - else - return 100; + return(ubCurrentProgress); } @@ -1607,6 +1637,36 @@ UINT8 CalcImportantSectorControl( void ) return( ubSectorControlPts ); } +// HEADROCK HAM B1: function to calculate how many "important sectors" there actually are in the game. This is +// used to fix the weight of strategic control importance on game progress, which was until now not really +// moddable. + +UINT8 CalcTotalImportantSectors( void ) +{ + UINT8 ubMapX, ubMapY; + UINT8 ubSectorControlPts = 0; + + + for ( ubMapX = 1; ubMapX < MAP_WORLD_X - 1; ubMapX++ ) + { + for ( ubMapY = 1; ubMapY < MAP_WORLD_Y - 1; ubMapY++ ) + { + // towns where militia can be trained and SAM sites are important sectors + if ( MilitiaTrainingAllowedInSector( ubMapX, ubMapY, 0 ) ) + { + ubSectorControlPts++; + + // SAM sites count double - they have no income, but have significant air control value + if ( IsThisSectorASAMSector( ubMapX, ubMapY, 0 ) ) + { + ubSectorControlPts++; + } + } + } + } + + return( ubSectorControlPts ); +} // WDS: Adding more ways to progress in the game // Count how many surface sectors the player has visited @@ -1627,6 +1687,24 @@ UINT16 CountSurfaceSectorsVisited( void ) return( ubSectorsVisited ); } +// HEADROCK: Count number of VISITABLE sectors +UINT16 TotalVisitableSurfaceSectors( void ) +{ + UINT8 ubMapX, ubMapY; + UINT16 ubVisitableSectors = 0; + + + for ( ubMapX = 1; ubMapX < MAP_WORLD_X - 1; ubMapX++ ) + { + for ( ubMapY = 1; ubMapY < MAP_WORLD_Y - 1; ubMapY++ ) + { + if( SectorInfo[ SECTOR(ubMapX,ubMapY) ].ubTraversability[ THROUGH_STRATEGIC_MOVE ] != GROUNDBARRIER && SectorInfo[ SECTOR(ubMapX,ubMapY) ].ubTraversability[ THROUGH_STRATEGIC_MOVE ] != EDGEOFWORLD ) + ++ubVisitableSectors; + } + } + + return( ubVisitableSectors ); +} void MERCMercWentUpALevelSendEmail( UINT8 ubMercMercIdValue ) { diff --git a/Tactical/Campaign.h b/Tactical/Campaign.h index 9e722f62..7caaa1c5 100644 --- a/Tactical/Campaign.h +++ b/Tactical/Campaign.h @@ -25,9 +25,13 @@ #define MAX_STAT_VALUE 100 // for stats and skills #define MAXEXPLEVEL 10 // maximum merc experience level -#define SKILLS_SUBPOINTS_TO_IMPROVE 25 -#define ATTRIBS_SUBPOINTS_TO_IMPROVE 50 -#define LEVEL_SUBPOINTS_TO_IMPROVE 350 // per current level! (Can't go over 6500, 10x must fit in USHORT!) +// HEADROCK HAM B2.7 : Externalizing these (changed from #define) +//#define SKILLS_SUBPOINTS_TO_IMPROVE 25 +//#define ATTRIBS_SUBPOINTS_TO_IMPROVE 50 +//#define LEVEL_SUBPOINTS_TO_IMPROVE 350 // per current level! (Can't go over 6500, 10x must fit in USHORT!) +extern UINT16 SKILLS_SUBPOINTS_TO_IMPROVE; +extern UINT16 ATTRIBS_SUBPOINTS_TO_IMPROVE; +extern UINT16 LEVEL_SUBPOINTS_TO_IMPROVE; #define WORKIMPROVERATE 2 // increase to make working mercs improve more #define TRAINIMPROVERATE 2 // increase to make training mercs improve more diff --git a/Tactical/DisplayCover.cpp b/Tactical/DisplayCover.cpp index 498e896d..3ea9c746 100644 --- a/Tactical/DisplayCover.cpp +++ b/Tactical/DisplayCover.cpp @@ -30,6 +30,8 @@ #include "Text.h" #include "strategicmap.h" #include "Render Fun.h" +// HEADROCK HAM B2.7: Allow calling a CTH approximation function for the CTH display ("F" key) +#include "UI Cursors.h" #endif @@ -635,7 +637,12 @@ void DisplayRangeToTarget( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo ) //AXP 30.03.2007: Fix CtH calculation for first shot after changing aim level (roof/ground) INT8 bTempTargetLevel = pSoldier->bTargetLevel; pSoldier->bTargetLevel = (INT8)gsInterfaceLevel; - uiHitChance = CalcChanceToHitGun( pSoldier, sTargetGridNo, (pSoldier->aiData.bShownAimTime ), pSoldier->bAimShotLocation ); + uiHitChance = CalcChanceToHitGun( pSoldier, sTargetGridNo, (INT8)(pSoldier->aiData.bShownAimTime ), pSoldier->bAimShotLocation ); + // HEADROCK HAM B2.7: CTH approximation? + if (gGameExternalOptions.fApproximateCTH) + { + uiHitChance = ChanceToHitApproximation( pSoldier, uiHitChance ); + } pSoldier->bTargetLevel = bTempTargetLevel; swprintf( zOutputString, zNewTacticalMessages[ TCTL_MSG__GUN_RANGE_AND_CTH ], Weapon[ pSoldier->inv[HANDPOS].usItem ].usRange / 10, uiHitChance ); diff --git a/Tactical/Handle UI.cpp b/Tactical/Handle UI.cpp index e93eedae..ccad78ab 100644 --- a/Tactical/Handle UI.cpp +++ b/Tactical/Handle UI.cpp @@ -230,7 +230,11 @@ INT16 gsTreeRevealYPos; // taken out of header to remove multiple symbol definitions (jonathanl) BOOLEAN gfUICtHBar; -UINT8 gbCtH; +// HEADROCK HAM B1/2: Created an array to hold CTH of different bullets for Burst/Auto CTH Bar. +UINT8 gbCtH[ 10 ]; +UINT8 gbCtHBurstCount; +// HEADROCK HAM B2: This value tracks whether Autofire mode is selected. This value may be redundant... not sure. +BOOLEAN gbCtHAutoFire; UINT16 gsTotalBulletCount; UINT16 gsBulletCount; BOOLEAN gTintBulletCounts; @@ -514,6 +518,8 @@ UINT32 HandleTacticalUI( void ) gTintBulletCounts = FALSE; gfUIAutofireBulletCount = FALSE; gfUICtHBar = FALSE; + // HEADROCK HAM B2: tells the cursor-drawing function to draw two CTH bars (for autofire). + gbCtHAutoFire = FALSE; // Set old event value guiOldEvent = uiNewEvent = guiCurrentEvent; diff --git a/Tactical/Handle UI.h b/Tactical/Handle UI.h index e05a97dd..16dab3b4 100644 --- a/Tactical/Handle UI.h +++ b/Tactical/Handle UI.h @@ -220,7 +220,11 @@ extern BOOLEAN gTintBulletCounts; extern BOOLEAN gfUIAutofireBulletCount; extern BOOLEAN gfUICtHBar; -extern UINT8 gbCtH; +// HEADROCK HAM B1/2: Multiple-bullet CTH bar array (replaces old single integer) +extern UINT8 gbCtH[ 10 ]; +extern UINT8 gbCtHBurstCount; +// HEADROCK HAM B2: Track autofire on/off +extern BOOLEAN gbCtHAutoFire; extern CHAR16 gzIntTileLocation[ 20 ]; extern BOOLEAN gfUIIntTileLocation; diff --git a/Tactical/Inventory Choosing.cpp b/Tactical/Inventory Choosing.cpp index a12520f3..031cc2e7 100644 --- a/Tactical/Inventory Choosing.cpp +++ b/Tactical/Inventory Choosing.cpp @@ -2241,7 +2241,8 @@ else // only enemy soldiers use auto-drop system! // don't use the auto-drop system in auto-resolve: player won't see what's being used & enemies will often win & keep'em - if ( SOLDIER_CLASS_ENEMY( bSoldierClass ) && !IsAutoResolveActive() ) + // HEADROCK HAM B2.8: Militia now drop all their equipment... IF not killed by player (see TurnSoldierIntoCorpse() ) + if ( (SOLDIER_CLASS_ENEMY( bSoldierClass ) || ( gGameExternalOptions.ubMilitiaDropEquipment > 0 && SOLDIER_CLASS_MILITIA( bSoldierClass ) )) && !IsAutoResolveActive() ) { // SPECIAL handling for weapons: we'll always drop a weapon type that has never been dropped before for( i = 0; i < pp->Inv.size(); i++ ) diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index 0a0f43a8..5982ff90 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -7102,6 +7102,9 @@ INT16 GetBurstToHitBonus( OBJECTTYPE * pObj, BOOLEAN fProneStance ) bonus += Item[pObj->usItem].bipod; bonus += BonusReduceMore( Item[pObj->usItem].bursttohitbonus, (*pObj)[0]->data.objectStatus ); + // HEADROCK HAM B2.5: A certain setting in the New Tracer System can turn auto/burst penalties off + // entirely, to make up for "Tracer Bump". + if ( gGameExternalOptions.iRealisticTracers != 1 ) bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].bursttohitbonus ; for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { @@ -7228,6 +7231,10 @@ INT16 GetAutoToHitBonus( OBJECTTYPE * pObj, BOOLEAN fProneStance ) bonus += BonusReduceMore( Item[pObj->usItem].autofiretohitbonus, (*pObj)[0]->data.objectStatus ); + // HEADROCK HAM B2.5: This external setting determines whether autofire penalty is affected by + // tracer ammo. At setting "1", it is disabled. This goes hand in hand with a new tracer effect that + // "bumps" CTH up after firing a tracer bullet. + if ( gGameExternalOptions.iRealisticTracers != 1 ) bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].autofiretohitbonus ; for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { @@ -8491,6 +8498,88 @@ UINT8 AllowedAimingLevels(SOLDIERTYPE * pSoldier) float iScopeBonus = 0; BOOLEAN allowed = TRUE; + // HEADROCK HAM B2.6: Dynamic aiming level restrictions based on gun type and attachments. + if ( gGameExternalOptions.fDynamicAimingTime ) + { + UINT16 weaponRange; + UINT8 weaponType, maxAimForType, maxAimWithoutBipod; + BOOLEAN fTwoHanded, fUsingBipod; + + // Read weapon data + fTwoHanded = Item[pSoldier->inv[pSoldier->ubAttackingHand].usItem].twohanded; + weaponRange = Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].usRange + GetRangeBonus(&pSoldier->inv[pSoldier->ubAttackingHand]); + weaponType = Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].ubWeaponType; + fUsingBipod = FALSE; + + maxAimWithoutBipod = 4; + + // Define basic (no attachments), and absolute maximums + if (weaponType == GUN_PISTOL || weaponType == GUN_M_PISTOL || (weaponType == GUN_SMG && fTwoHanded == 0) || fTwoHanded == 0) + { + maxAimForType = 2; + aimLevels = 1; + maxAimWithoutBipod = 2; + } + else if (weaponType == GUN_SHOTGUN || weaponType == GUN_LMG || (weaponType == GUN_SMG && fTwoHanded == 1)) + { + maxAimForType = 3; + aimLevels = 2; + maxAimWithoutBipod = 3; + } + else if ((weaponType == GUN_AS_RIFLE || weaponType == GUN_RIFLE || weaponType == GUN_SN_RIFLE) && weaponRange <= 500) + { + maxAimForType = 4; + aimLevels = 2; + maxAimWithoutBipod = 3; + } + else if ((weaponType == GUN_AS_RIFLE || weaponType == GUN_RIFLE || weaponType == GUN_SN_RIFLE) && weaponRange > 500) + { + maxAimForType = 8; + aimLevels = 3; + maxAimWithoutBipod = 4; + } + else + { + return 4; + } + + // Determine whether a bipod is being used (prone) + if (GetBipodBonus(&pSoldier->inv[pSoldier->ubAttackingHand])>0 && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE ) + { + fUsingBipod = TRUE; + } + + iScopeBonus = ( (float)gGameExternalOptions.ubStraightSightRange * GetMinRangeForAimBonus(&pSoldier->inv[pSoldier->ubAttackingHand]) / 100 ); + + if ( iScopeBonus >= ( (float)gGameExternalOptions.ubStraightSightRange * 0.6) ) // >= 60% of sight range (~9 tiles by default) + { + aimLevels = (UINT8)((float)aimLevels * (float)2); + } + + else if ( iScopeBonus >= ( (float)gGameExternalOptions.ubStraightSightRange * 0.3) ) // >= 30% of sight range (~4 tiles by default) + { + aimLevels = (UINT8)((float)(aimLevels+1) * (float)1.5); + } + + // Smaller scopes increase by one. + else if ( iScopeBonus > 0 ) + { + aimLevels++; + } + + // Make sure not over maximum allowed for weapon type. + if (aimLevels > maxAimForType) + { + aimLevels = maxAimForType; + } + // Make sure not over maximum allowed without a bipod. + if (!fUsingBipod) + { + aimLevels = __min(aimLevels, maxAimWithoutBipod); + } + } + else // JA2 1.13 Basic aiming restrictions (8 levels for 10x scope, 6 levels for 7x scope) + { if ( gGameSettings.fOptions[TOPTION_AIM_LEVEL_RESTRICTION] && Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].ubWeaponType != GUN_RIFLE && Weapon[pSoldier->inv[pSoldier->ubAttackingHand].usItem].ubWeaponType != GUN_SN_RIFLE ) allowed = FALSE; @@ -8506,6 +8595,7 @@ UINT8 AllowedAimingLevels(SOLDIERTYPE * pSoldier) if ( iScopeBonus >= ( (float)gGameExternalOptions.ubStraightSightRange * 0.6) ) // >= 60% of sight range (~9 tiles by default) { aimLevels += 2; + } } } diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index 52688061..2ca938c3 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -3669,6 +3669,24 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA usBulletFlags |= BULLET_FLAG_FLAME; ubSpreadIndex = 2; } + // HEADROCK HAM B2.5: Set tracer effect on/off for individual bullets in a Tracer Magazine, as part of the + // New Tracer System. + else if (gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) + && AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect ) + { + UINT16 iBulletsLeft, iBulletsPerTracer; + iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; + iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; + + if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) + { + fTracer = TRUE; + } + else + { + fTracer = FALSE; + } + } else if ( AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect && (pFirer->bDoBurst || gGameSettings.fOptions[ TOPTION_TRACERS_FOR_SINGLE_FIRE ]) ) { //usBulletFlags |= BULLET_FLAG_TRACER; @@ -3817,6 +3835,26 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA // this distance limit only applies in a "hard" sense to fake bullets for chance-to-get-through, // but is used for determining structure hits by the regular code pBullet->iDistanceLimit = iDistance; + // HEADROCK HAM BETA2.5: New method for signifying whether a bullet is a tracer or not, using an individual + // bullet structure flag. Hehehehe, I think this is kind of reverting to old code, isn't it? + if (gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) + && AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect ) + { + UINT16 iBulletsLeft, iBulletsPerTracer; + iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; + iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; + + // Is this specific bullet a tracer? - based on how many tracers there are per regular bullets in + // a tracer magazine (INI-settable). + if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) + { + pBullet->fTracer = TRUE; + } + else + { + pBullet->fTracer = FALSE; + } + } if (fFake) { bCTGT = FireBullet( pFirer, pBullet, TRUE ); @@ -4366,7 +4404,10 @@ void MoveBullet( INT32 iBullet ) } } - if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER*/ ) ) || fTracer == TRUE) + // HEADROCK HAM B2.5: Changed condition to read fTracer flag directly from bullet's struct. + // This is for the New Tracer System. + if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER*/ ) ) + || ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE))) { INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS; @@ -4653,7 +4694,10 @@ void MoveBullet( INT32 iBullet ) pBullet->iCurrCubesZ = CONVERT_HEIGHTUNITS_TO_INDEX( FIXEDPT_TO_INT32( pBullet->qCurrZ ) ); pBullet->iLoop++; - if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER */) ) || fTracer == TRUE) + // HEADROCK HAM B2.5: Changed condition to read fTracer flag directly from bullet's struct. + // This is for the New Tracer System. + if (( pBullet->usFlags & ( BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME | BULLET_FLAG_CREATURE_SPIT /*| BULLET_FLAG_TRACER */) ) + || ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE))) { INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS; diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 34c4c7f0..de17e6e6 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -7206,12 +7206,24 @@ INT8 CalcSuppressionTolerance( SOLDIERTYPE * pSoldier ) void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) { + /////////////////////////////////////////////////////////////////////////////// + // + // HEADROCK HAM B2: This entire function has been completely revamped. + // + /////////////////////////////////////////////////////////////////////////////// + + // External options. + UINT8 MAX_APS_SUPPRESSED = gGameExternalOptions.iMaxSuppressionAPLossPerAttack; + UINT8 MAX_APS_SUPPRESSED_TOTAL = gGameExternalOptions.iMaxSuppressionAPLossPerTurn; + INT8 SUPPRESSION_AP_LIMIT = gGameExternalOptions.iMinAPLimitFromSuppression; + INT8 MAXIMUM_SUPPRESSION_SHOCK = gGameExternalOptions.iMaxSuppressionShock; INT8 bTolerance; INT16 sClosestOpponent, sClosestOppLoc; UINT8 ubPointsLost, ubTotalPointsLost, ubNewStance; UINT32 uiLoop; UINT8 ubLoop2; - UINT16 uiLoop3; + // Flag to determine if the target is cowering (if allowed) + BOOLEAN fCower; SOLDIERTYPE * pSoldier; for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) @@ -7226,7 +7238,14 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: figure out aps lost")); // multiply by 2, add 1 and divide by 2 to round off to nearest whole number - ubPointsLost = (UINT8)( (FLOAT)((pSoldier->ubSuppressionPoints * APBPConstants[AP_SUPPRESSION_MOD]) / (bTolerance + 6)) * 2 + 1 ) / 2; + // HEADROCK: Note that suppression points accumulate by bullets flying near the character. It includes + // friendly fire at a certain distance. Also note that it may be reset either each attack or each turn + // (based on new INI settings), which has a small effect on how suppression builds up (but nothing + // major). + ubPointsLost = ( ( (pSoldier->ubSuppressionPoints * APBPConstants[AP_SUPPRESSION_MOD]) / (bTolerance + 6) ) * 2 + 1 ) / 2; + + // HEADROCK HAM Beta 2.2: SuppressionEffectiveness acts as a percentage for the number of lost APs. + ubPointsLost = ( ubPointsLost * gGameExternalOptions.iSuppressionEffectiveness ) / 100; // reduce loss of APs based on stance // ATE: Taken out because we can possibly supress ourselves... @@ -7243,37 +7262,125 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) //} // cap the # of APs we can lose - if (ubPointsLost > APBPConstants[AP_MAX_SUPPRESSED]) + // HEADROCK HAM B2: This now reads an external value. 0 means no limit. + if (MAX_APS_SUPPRESSED > 0) { - ubPointsLost = (UINT8)APBPConstants[AP_MAX_SUPPRESSED]; + if (ubPointsLost > APBPConstants[AP_MAX_SUPPRESSED]) + { + ubPointsLost = APBPConstants[AP_MAX_SUPPRESSED]; + } } + // HEADROCK HAM B2: This makes sure that we never lose more APs than we're allowed per turn, + if (MAX_APS_SUPPRESSED_TOTAL > 0) + { + if (pSoldier->ubAPsLostToSuppression + ubPointsLost > MAX_APS_SUPPRESSED_TOTAL) + { + ubPointsLost = MAX_APS_SUPPRESSED_TOTAL - pSoldier->ubAPsLostToSuppression; + } + } + // Keeps a number for later reference ubTotalPointsLost = ubPointsLost; - // Subtract off the APs lost before this point to find out how many points are lost now + // Make sure we're suffering extra AP loss at all. If the number of APs we're supposed to lose now + // is equal/higher to the number of APs we've already lost, it means we haven't actually gained any + // suppression. This is very important, as this function runs EVERY TIME an attack, ANY attack, ends. + // If the soldier hasn't suffered suppression since the last time this check ran, we'll hit the + // "continue" command. Note that with a certain INI setting, this value is reset after each attack + // or after each turn, but overall the function acts the same in both cases. if (pSoldier->ubAPsLostToSuppression >= ubPointsLost) { continue; } + // APs actually lost will be the difference between the potential loss and the APs we've already lost + // so far. Would theoretically equal 0 if we haven't suffered sufficient extra suppression this turn to + // cause any AP loss, but then we would've already hit the "continue" command, above. + ubPointsLost -= pSoldier->ubAPsLostToSuppression; + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: check for morale effects")); - // morale modifier - if (ubTotalPointsLost / 2 > pSoldier->ubAPsLostToSuppression / 2) + + // HEADROCK HAM B2: This nifty little bit gives suppression an "extra kick". Soldiers affected by + // suppression (I.E. lost APs) will also suffer from SHOCK. This is similar to getting shot but + // without the health/stamina loss - the soldier will lose 5% CTH for every point of shock they suffer. + // Shock is sliced in half at the start of every turn. Also note that shock may cause "cowering", and + // may also reduce any attacker's aim as well! + if (gGameExternalOptions.fSuppressionShock) { - //CHRISL: This should dynamically adjust the number of times we run the morale loop based on AP_MAXIMUM - uiLoop3 = (((ubTotalPointsLost / 2) - (pSoldier->ubAPsLostToSuppression / 2)) * APBPConstants[AP_MAXIMUM]) / 100; - for ( ubLoop2 = 0; ubLoop2 < uiLoop3; ubLoop2++ ) + if (ubPointsLost > 0) + { + // Experienced and/or high-morale soldiers suffer less shock than others. + INT8 bShockValue, bShockLimit; + bShockLimit = MAXIMUM_SUPPRESSION_SHOCK - bTolerance; + // the amount of shock received depends mainly on how many APs we've lost. 8 here is arbitrary, + // as the shock loss can later be adjusted by the external modifier below. + bShockValue = (bShockLimit * ubPointsLost) / 8; + + if (bShockValue < 0) + bShockValue = 0; + if (bShockLimit < 0) + bShockLimit = 0; + + // use external value to determine how effective SHOCK really is. + bShockValue = (bShockValue * gGameExternalOptions.iSuppressionShockEffectiveness) / 100; + + // Make sure total shock doesn't go TOO high. Maximum is around 30 (for a CTH effect of -150!), + // including previous shock from suppression and/or wounds. The Maximum is mitigated by + // the character's experience and morale. The shock value can bump above that level + // momentarily, for particularily lousy characters, rendering them practically incapable of firing + // back effectively... But of course, it is halved once the character starts his next turn, so + // shock at turnstart will usually be no higher than 15. + + if ( pSoldier->aiData.bShock + bShockValue <= bShockLimit ) + { + // Shock limit not yet breached. Add shock to character. + pSoldier->aiData.bShock += bShockValue; + } + else if ( pSoldier->aiData.bShock < bShockLimit ) // Shock limit will be breached. + { + // Original shock was lower than the limit, so add extra shock and breach the limit. + pSoldier->aiData.bShock += bShockValue; + } + // Else, original shock was already over the limit. No more shock is added. + } + } + // HEADROCK: Untrained characters won't react well to being fired at (they'll keep standing), but if + // Suppression Shock is activated, they may dive and "COWER" anyway. For this to happen, they must first + // make a check to see if they are in enough shock to cower. + + fCower = false; + if ( gGameExternalOptions.fSuppressionShock && gGameExternalOptions.iAimPenaltyPerTargetShock > 0 ) + { + if (pSoldier->aiData.bShock >= bTolerance) + { fCower = true; } + } + + // HEADROCK HAM B2: If enemy cowers in fear, let the player know. + if (fCower) + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113HAMMessage[0], pSoldier->name ); + // If cowering, increase suppression effectiveness by external percentage + if (gGameExternalOptions.iCowerEffectOnSuppression > 0) + ubPointsLost = (ubPointsLost * gGameExternalOptions.iCowerEffectOnSuppression) / 100; + } + // morale modifier + // For every 2 APs lost, subtract morale by a certain value. + // HEADROCK: Modified - now externalized to INI. + if (gGameExternalOptions.iAPLostPerMoraleDrop > 0 && ubPointsLost > 0) + { + for ( ubLoop2 = 0; ubLoop2 < (ubPointsLost / gGameExternalOptions.iAPLostPerMoraleDrop); ubLoop2++ ) { HandleMoraleEvent( pSoldier, MORALE_SUPPRESSED, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); } } + - ubPointsLost -= pSoldier->ubAPsLostToSuppression; ubNewStance = 0; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: check for reaction")); // merc may get to react - if ( pSoldier->ubSuppressionPoints >= ( 130 / (6 + bTolerance) ) ) + // Headrock: apply suppression effectiveness percentage to this value. + if ( (pSoldier->ubSuppressionPoints * gGameExternalOptions.iSuppressionEffectiveness) / 100 >= ( 130 / (6 + bTolerance) ) || fCower ) { // merc gets to use APs to react! switch (gAnimControl[ pSoldier->usAnimState ].ubEndHeight) @@ -7285,7 +7392,8 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) if (ubTotalPointsLost >= APBPConstants[AP_PRONE] && IsValidStance( pSoldier, ANIM_PRONE ) ) { sClosestOpponent = ClosestKnownOpponent( pSoldier, &sClosestOppLoc, NULL ); - if (sClosestOpponent == NOWHERE || SpacesAway( pSoldier->sGridNo, sClosestOppLoc ) > 8) + // HEADROCK: Added cowering. + if (sClosestOpponent == NOWHERE || SpacesAway( pSoldier->sGridNo, sClosestOppLoc ) > 8 || fCower) { if (ubPointsLost < APBPConstants[AP_PRONE]) { @@ -7311,7 +7419,8 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) else if (ubTotalPointsLost >= (APBPConstants[AP_CROUCH] + APBPConstants[AP_PRONE]) && ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_PRONE ) && IsValidStance( pSoldier, ANIM_PRONE ) ) { sClosestOpponent = ClosestKnownOpponent( pSoldier, &sClosestOppLoc, NULL ); - if ( sClosestOpponent == NOWHERE || SpacesAway( pSoldier->sGridNo, sClosestOppLoc ) > 8 ) + // HEADROCK: Added cowering. + if ( sClosestOpponent == NOWHERE || SpacesAway( pSoldier->sGridNo, sClosestOppLoc ) > 8 ||fCower ) { if ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_STAND ) { @@ -7341,7 +7450,18 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: reduce action points")); // Reduce action points! - pSoldier->bActionPoints -= ubPointsLost; + // HEADROCK HAM Beta 2.2: Enforce a minimum limit via INI. + if (pSoldier->bActionPoints - ubPointsLost < gGameExternalOptions.iMinAPLimitFromSuppression ) + { + pSoldier->bActionPoints = gGameExternalOptions.iMinAPLimitFromSuppression; + } + else + { + pSoldier->bActionPoints -= ubPointsLost; + } + // Remember how many APs were lost. This prevents us from losing more and more APs without receiving + // extra suppression. Note that a specific HAM setting will reset this value after every attack, + // but also resets ubSuppressionPoints. pSoldier->ubAPsLostToSuppression = ubTotalPointsLost; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: check for quote")); @@ -7404,6 +7524,18 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) pSoldier->flags.fDontChargeAPsForStanceChange = TRUE; } + // HEADROCK HAM B2: Optional fix for suppression. This clears up the value that measures suppression + // accumulated so far. Previously, the value was NEVER cleared, which means that a character could + // only be suppressed ONCE in the game (unless they die or get deleted). + // With the setting at "2", these two values are cleared every turn. Therefore, suppression doesn't + // accumulate at all, only its effects are cumulative. In the end, it functions almost the same as + // clearing every turn, but I added this as an alternative. + if (gGameExternalOptions.iClearSuppression == 2) + { + pSoldier->ubAPsLostToSuppression = 0; + pSoldier->ubSuppressionPoints = 0; + } + } // end of examining one soldier } // end of loop @@ -7988,6 +8120,17 @@ SOLDIERTYPE *InternalReduceAttackBusyCount( ) pSoldier->flags.fGettingHit = FALSE; } + // HEADROCK HAM B2: Optional fix for suppression. This clears up the value that measures suppression + // accumulated so far. Previously, the value was NEVER cleared, which means that a character could + // only be suppressed ONCE in the game (unless they die or get deleted). + // With the setting at "2", these two values are cleared every turn. Therefore, suppression doesn't + // accumulate at all, only its effects are cumulative. In the end, it functions almost the same as + // clearing every turn, but I added this as an alternative. + if (gGameExternalOptions.iClearSuppression == 2) + { + pSoldier->ubAPsLostToSuppression = 0; + pSoldier->ubSuppressionPoints = 0; + } if (pSoldier->ubAttackerID != NOBODY ) { if (pSoldier->ubPreviousAttackerID != pSoldier->ubAttackerID) diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index 158926d0..fa1c116a 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -316,6 +316,8 @@ void ResetAllMercSpeeds( ); BOOLEAN HandleGotoNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving, BOOLEAN fInitialMove, UINT16 usAnimState ); SOLDIERTYPE * ReduceAttackBusyCount( ); +// HEADROCK HAM B2.6: Made this public so it can be used elsewhere. +INT8 CalcSuppressionTolerance( SOLDIERTYPE * pSoldier ); void CommonEnterCombatModeCode( ); diff --git a/Tactical/Points.cpp b/Tactical/Points.cpp index 053ebe1d..bd49f789 100644 --- a/Tactical/Points.cpp +++ b/Tactical/Points.cpp @@ -1122,7 +1122,43 @@ INT16 CalcTotalAPsToAttack( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubAddTur } else { - sAPCost += (bAimTime*APBPConstants[AP_CLICK_AIM]); + if (gGameExternalOptions.fIncreasedAimingCost ) + { + // HEADROCK HAM B2.6: Changed the number of APs to attack when aiming. + if (bAimTime > 0) + { + // Add 1/2 ready cost (rounded up) for getting the sights up to the eye + sAPCost += ((Weapon[ usItemNum ].ubReadyTime * (100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[HANDPOS])) / 100) + 1) / 2; + + // Add regular aim time for the first 4 aiming actions. + sAPCost += __min(bAimTime,4); + + // If the weapon has a scope, and the target is within eligible range for scope use + if ( IsScoped(&pSoldier->inv[HANDPOS]) + && GetRangeInCellCoordsFromGridNoDiff( pSoldier->sGridNo, sGridNo ) >= GetMinRangeForAimBonus(&pSoldier->inv[HANDPOS]) ) + { + // Add time to adjust eye to scope + if (bAimTime > 0) + { + sAPCost += 1; + } + // Add 2 APs for each aiming point between 5 and 6 + if (bAimTime > 4) + { + sAPCost += __min(((bAimTime - 4) * 2), 4); + } + // Add 3 APs for each aiming point beyond 6. + if (bAimTime > 6) + { + sAPCost += (bAimTime - 6) * 3; + } + } + } + } + else + { + sAPCost += bAimTime; + } } } diff --git a/Tactical/Rotting Corpses.cpp b/Tactical/Rotting Corpses.cpp index e45ffc7e..46950e6e 100644 --- a/Tactical/Rotting Corpses.cpp +++ b/Tactical/Rotting Corpses.cpp @@ -932,9 +932,12 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE if ( !(Item[ pObj->usItem ].defaultundroppable ) ) { ReduceAmmoDroppedByNonPlayerSoldiers( pSoldier, cnt ); - //if this soldier was an enemy - // Kaiden: Added from UB's reveal all items after combat feature! - if( pSoldier->bTeam == ENEMY_TEAM ) + //if this soldier was an enemy + // Kaiden: Added from UB's reveal all items after combat feature! + // HEADROCK HAM B2.8: Now also reveals equipment dropped by militia, if requirement is met. + if( pSoldier->bTeam == ENEMY_TEAM || + ( gGameExternalOptions.ubMilitiaDropEquipment == 2 && pSoldier->bTeam == MILITIA_TEAM ) || + ( gGameExternalOptions.ubMilitiaDropEquipment == 1 && pSoldier->bTeam == MILITIA_TEAM && Menptr[ pSoldier->ubAttackerID ].bTeam != OUR_TEAM )) { //add a flag to the item so when all enemies are killed, we can run through and reveal all the enemies items usItemFlags |= WORLD_ITEM_DROPPED_FROM_ENEMY; @@ -946,7 +949,12 @@ BOOLEAN TurnSoldierIntoCorpse( SOLDIERTYPE *pSoldier, BOOLEAN fRemoveMerc, BOOLE } } + // HEADROCK HAM B2.8: Militia will drop items only if allowed. + if (!(gGameExternalOptions.ubMilitiaDropEquipment == 0 && pSoldier->bTeam == MILITIA_TEAM ) && + !(gGameExternalOptions.ubMilitiaDropEquipment == 1 && pSoldier->bTeam == MILITIA_TEAM && Menptr[ pSoldier->ubAttackerID ].bTeam == OUR_TEAM )) + { AddItemToPool( pSoldier->sGridNo, pObj, bVisible , pSoldier->pathing.bLevel, usItemFlags, -1 ); + } } } } diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index e1c5826f..ffb88a51 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -1849,8 +1849,9 @@ INT16 SOLDIERTYPE::CalcActionPoints( void ) ubPoints = DynamicAdjustAPConstants(ubPoints, ubPoints); // If resulting APs are below our permitted minimum, raise them to it! - if (ubPoints < APBPConstants[AP_MINIMUM]) - ubPoints = APBPConstants[AP_MINIMUM]; + // HEADROCK: Enforce new minimums due to suppression. I should've done this neater though. + if (ubPoints < gGameExternalOptions.iMinAPLimitFromSuppression) + ubPoints = gGameExternalOptions.iMinAPLimitFromSuppression; // make sure action points doesn't exceed the permitted maximum ubMaxAPs = gubMaxActionPoints[ this->ubBodyType ]; @@ -6688,6 +6689,15 @@ void SOLDIERTYPE::EVENT_BeginMercTurn( BOOLEAN fFromRealTime, INT32 iRealTimeCou this->usQuoteSaidExtFlags &= ( ~SOLDIER_QUOTE_SAID_EXT_CLOSE_CALL ); this->bNumHitsThisTurn = 0; this->ubSuppressionPoints = 0; + // HEADROCK HAM B2: Optional fix for suppression. This clears up the value that measures suppression + // accumulated so far. Previously, the value was NEVER cleared, which means that a character could + // only be suppressed ONCE in the game (unless they die or get deleted). There's really no reason to + // keep this in an IF statement though, as it should, by all rights, erase itself each turn at the very + // least to avoid the once-in-a-lifetime suppression problem. + if (gGameExternalOptions.iClearSuppression == 1) + { + this->ubAPsLostToSuppression = 0; + } this->flags.fCloseCall = FALSE; this->ubMovementNoiseHeard = 0; diff --git a/Tactical/Turn Based Input.cpp b/Tactical/Turn Based Input.cpp index 4fbc2cbc..fe09c53a 100644 --- a/Tactical/Turn Based Input.cpp +++ b/Tactical/Turn Based Input.cpp @@ -192,6 +192,8 @@ extern INT16 ITEMDESC_START_Y; //Little functions called by keyboard input void SwapGoggles(SOLDIERTYPE *pTeamSoldier); +// HEADROCK HAM B2.8: Function to switch team's goggles uniformly +void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision); void SeperateItems(); void StackAndSort( BOOLEAN fRestrictToAmmo ); void CreateRandomItem(); @@ -3315,10 +3317,43 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) case 'N': SOLDIERTYPE *pTeamSoldier; INT8 bLoop; - for (bLoop=gTacticalStatus.Team[gbPlayerNum].bFirstID, pTeamSoldier=MercPtrs[bLoop]; bLoop <= gTacticalStatus.Team[gbPlayerNum].bLastID; bLoop++, pTeamSoldier++) + BOOLEAN fToNightVision; + + // HEADROCK HAM B2.8: Added call for CTRL-SHIFT-N to switch all soldiers to day/night. + if ( fCtrl ) { - if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->bAssignment == CurrentSquad( ) && !AM_A_ROBOT( pTeamSoldier ) ) { - SwapGoggles(pTeamSoldier); + for (bLoop=gTacticalStatus.Team[gbPlayerNum].bFirstID, pTeamSoldier=MercPtrs[bLoop]; bLoop <= gTacticalStatus.Team[gbPlayerNum].bLastID; bLoop++, pTeamSoldier++) + { + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->bAssignment == CurrentSquad( ) && !AM_A_ROBOT( pTeamSoldier ) ) + { + if ( pTeamSoldier->inv[HEAD1POS].exists() || pTeamSoldier->inv[HEAD2POS].exists() ) + { + fToNightVision = ( Item[pTeamSoldier->inv[HEAD1POS].usItem].brightlightvisionrangebonus > 0 || Item[pTeamSoldier->inv[HEAD2POS].usItem].brightlightvisionrangebonus > 0 ); + break; + } + if ( bLoop == gTacticalStatus.Team[gbPlayerNum].bLastID ) + { + // Default to night or day based on game hour + fToNightVision = NightTime(); + } + } + } + for (bLoop=gTacticalStatus.Team[gbPlayerNum].bFirstID, pTeamSoldier=MercPtrs[bLoop]; bLoop <= gTacticalStatus.Team[gbPlayerNum].bLastID; bLoop++, pTeamSoldier++) + { + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->bAssignment == CurrentSquad( ) && !AM_A_ROBOT( pTeamSoldier ) ) + { + SwapGogglesUniformly(pTeamSoldier, fToNightVision); + } + } + } + else // Shift-N + { + for (bLoop=gTacticalStatus.Team[gbPlayerNum].bFirstID, pTeamSoldier=MercPtrs[bLoop]; bLoop <= gTacticalStatus.Team[gbPlayerNum].bLastID; bLoop++, pTeamSoldier++) + { + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->bAssignment == CurrentSquad( ) && !AM_A_ROBOT( pTeamSoldier ) ) + { + SwapGoggles(pTeamSoldier); + } } } break; @@ -5638,81 +5673,186 @@ void SwapGoggles(SOLDIERTYPE *pTeamSoldier) /* CHRISL - Adjusted this option to allow the game to search through Helmet attachments as well as inventory positions. */ - - // WDS - Smart goggle switching - // NOTE: Investigate using GetItemVisionRangeBonus from Items.cpp??? - if (gGameExternalOptions.smartGoggleSwitch) { - // Look through the head slots and find any sort of goggle or an empty spot - int slotToUse = -1; - for (int headSlot = HEAD1POS; headSlot <= HEAD2POS; ++headSlot) { - if ( (Item[pTeamSoldier->inv[headSlot].usItem].brightlightvisionrangebonus > 0) ) { - slotToUse = headSlot; - break; - } else if ( (Item[pTeamSoldier->inv[headSlot].usItem].nightvisionrangebonus > 0) ) { - slotToUse = headSlot; - break; - } else if (pTeamSoldier->inv[headSlot].exists() == false) { - slotToUse = headSlot; + OBJECTTYPE * pObj; + OBJECTTYPE * pGoggles = NULL; + INT8 bSlot1; + int bestBonus; + bool itemFound = false; + //CHRISL: Before doing anything, we should look at both head slots to see if either slot has some sort of goggles + for(bSlot1 = HEAD1POS; bSlot1 <= HEAD2POS; bSlot1++) + { + if(Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus > 0) + itemFound = true; + if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0) + itemFound = true; + } + //2 head slots + for (bSlot1 = HEAD1POS; bSlot1 <= HEAD2POS; bSlot1++) + { + // if wearing sungoggles + if ( Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus > 0 ) + { + itemFound = true; + bestBonus = 0; + pGoggles = FindNightGogglesInInv( pTeamSoldier ); + //search for better goggles on the helmet + if (pGoggles) + { + bestBonus = Item[pGoggles->usItem].nightvisionrangebonus; } - } - if (slotToUse == -1) { - // No place to swap in a new goggle, give up - return; - } - - // Find the best goggles for the current time of day anywhere in inventory - OBJECTTYPE * pGoggles = 0; - if (DayTime()) { - pGoggles = FindSunGogglesInInv( pTeamSoldier, TRUE ); - } else { - pGoggles = FindNightGogglesInInv( pTeamSoldier, TRUE ); - } - - if (pGoggles) { - // Now either swap or equip the best one that was found - if (pTeamSoldier->inv[slotToUse].exists()) { - SwapObjs( pTeamSoldier, slotToUse, pGoggles, TRUE ); - } else { - pGoggles->MoveThisObjectTo(pTeamSoldier->inv[slotToUse], 1, pTeamSoldier, slotToUse); - } - } else { - // No goggles to equip, should the current ones be unequiped? - if (pTeamSoldier->inv[slotToUse].exists()) { - if ((DayTime() && (Item[pTeamSoldier->inv[slotToUse].usItem].nightvisionrangebonus > 0)) || - (!DayTime() && (Item[pTeamSoldier->inv[slotToUse].usItem].brightlightvisionrangebonus > 0))) { - // It's day and we're wearing night goggles (or vice-versa), find a place to stash them - if (pTeamSoldier->inv[ HELMETPOS ].exists()) { - if (pTeamSoldier->inv[ HELMETPOS ].AttachObject( NULL, &pTeamSoldier->inv[slotToUse] )) { - // It worked! - } else { - // Try dumping it anywhere in inventory because it doesn't attach to the helmet - } - } else { - // Try dumping it anywhere in inventory given there's no helemt + //search helmet and vest + for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) + { + pObj = &(pTeamSoldier->inv[gear]); + for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) + { + if ( Item[ iter->usItem ].nightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE ) + { + pGoggles = &(*iter); + bestBonus = Item[ iter->usItem ].nightvisionrangebonus; } } } - } - } else { - // Normal goggle switching - OBJECTTYPE * pObj; - OBJECTTYPE * pGoggles = NULL; - INT8 bSlot1; - int bestBonus; - bool itemFound = false; - - //CHRISL: Before doing anything, we should look at both head slots to see if either slot has some sort of goggles - for (bSlot1 = HEAD1POS; bSlot1 <= HEAD2POS; bSlot1++) { - if ((Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus > 0) || - (Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0)) - itemFound = true; - } - //2 head slots - for (bSlot1 = HEAD1POS; bSlot1 <= HEAD2POS; bSlot1++) - { - // if wearing sungoggles - if ( (Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus > 0) ) + if ( pGoggles ) { + SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); + break; + } + // HEADROCK HAM B2.8: If no goggles were found to switch to, the character will remove what they're + // wearing, to avoid situations where a character refuses to remove the wrong set of goggles and + // thus suffers a penalty. + else + { + // Remove sungoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + break; + } + } + // else if wearing NVGs + else if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0) + { + itemFound = true; + bestBonus = 0; + pGoggles = FindSunGogglesInInv( pTeamSoldier ); + //search for better goggles on the helmet + if (pGoggles) + { + bestBonus = Item[pGoggles->usItem].brightlightvisionrangebonus; + } + //search helmet and vest + for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) + { + pObj = &(pTeamSoldier->inv[gear]); + for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) + { + if ( Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE ) + { + pGoggles = &(*iter); + bestBonus = Item[ iter->usItem ].brightlightvisionrangebonus; + } + } + } + if ( pGoggles ) + { + SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); + break; + } + // HEADROCK HAM B2.8: If no goggles were found to switch to, the character will remove what they're + // wearing, to avoid situations where a character refuses to remove the wrong set of goggles and + // thus suffers a penalty. + else + { + // Remove nightgoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + break; + } + } + // else if not wearing anything and no goggles found + else if(itemFound == false && pTeamSoldier->inv[bSlot1].exists() == false) + { + bestBonus = 0; + // search helmet and vest for goggles of some kind + for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) + { + pObj = &(pTeamSoldier->inv[gear]); + for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) + { + if(DayTime() == TRUE && Item[iter->usItem].brightlightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) + { + pGoggles = &(*iter); + bestBonus = Item[iter->usItem].brightlightvisionrangebonus; + } + else if(NightTime() == TRUE && Item[iter->usItem].nightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) + { + pGoggles = &(*iter); + bestBonus = Item[iter->usItem].nightvisionrangebonus; + } + } + if(pGoggles) + { + pGoggles->MoveThisObjectTo(pTeamSoldier->inv[bSlot1], 1, pTeamSoldier, bSlot1); + pObj->RemoveAttachment(pGoggles); + break; + } + } + if(pTeamSoldier->inv[bSlot1].exists() == false) + { + if(DayTime() == TRUE) + pGoggles = FindSunGogglesInInv( pTeamSoldier ); + else + pGoggles = FindNightGogglesInInv( pTeamSoldier ); + if(pGoggles) + { + SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); + break; + } + } + else + { + break; + } + } + } + fCharacterInfoPanelDirty = TRUE; + fTeamPanelDirty = TRUE; + fInterfacePanelDirty = DIRTYLEVEL2; + pTeamSoldier->DeleteSoldierLight( ); + pTeamSoldier->PositionSoldierLight( ); +} + +// HEADROCK HAM B2.8: This function does the same as SwapGoggles, with a twist. It changes all worn goggles in the +// team uniformly, so that everyone is wearing either nightvision or dayvision, regardless of what they were wearing +// before. The check is based on the headgear of the first character in the group (he'll switch, and everyone else +// will switch to match him. + +void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision) +{ + /* CHRISL - Adjusted this option to allow the game to search through Helmet attachments + as well as inventory positions. + */ + OBJECTTYPE * pObj; + OBJECTTYPE * pGoggles = NULL; + INT8 bSlot1; + int bestBonus; + bool itemFound = false; + + //CHRISL: Before doing anything, we should look at both head slots to see if either slot has some sort of goggles + for(bSlot1 = HEAD1POS; bSlot1 <= HEAD2POS; bSlot1++) + { + if(Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus > 0) + itemFound = true; + if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0) + itemFound = true; + } + //2 head slots + for (bSlot1 = HEAD1POS; bSlot1 <= HEAD2POS; bSlot1++) + { + // if wearing sungoggles + if ( Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus > 0 ) + { + if (fToNightVision == TRUE) // Only if we want to switch to nightvision! + { + itemFound = true; bestBonus = 0; pGoggles = FindNightGogglesInInv( pTeamSoldier ); //search for better goggles on the helmet @@ -5738,10 +5878,20 @@ void SwapGoggles(SOLDIERTYPE *pTeamSoldier) SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); break; } + else if (Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus <= 0) + { + // Remove sungoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + break; + } } - // else if wearing NVGs - else if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0 ) + } + // else if wearing NVGs + else if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0) + { + if (fToNightVision == FALSE) // Only if we want to switch to dayvision! { + itemFound = true; bestBonus = 0; pGoggles = FindSunGogglesInInv( pTeamSoldier ); //search for better goggles on the helmet @@ -5767,57 +5917,60 @@ void SwapGoggles(SOLDIERTYPE *pTeamSoldier) SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); break; } - } - // else if not wearing anything and no goggles found - else if(itemFound == false && pTeamSoldier->inv[bSlot1].exists() == false) - { - bestBonus = 0; - // search helmet and vest for goggles of some kind - for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) - { - pObj = &(pTeamSoldier->inv[gear]); - for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) - { - if(DayTime() == TRUE && Item[iter->usItem].brightlightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) - { - pGoggles = &(*iter); - bestBonus = Item[iter->usItem].brightlightvisionrangebonus; - } - else if(NightTime() == TRUE && Item[iter->usItem].nightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) - { - pGoggles = &(*iter); - bestBonus = Item[iter->usItem].nightvisionrangebonus; - } - } - if(pGoggles) - { - pGoggles->MoveThisObjectTo(pTeamSoldier->inv[bSlot1], 1, pTeamSoldier, bSlot1); - pObj->RemoveAttachment(pGoggles); - break; - } - } - if(pTeamSoldier->inv[bSlot1].exists() == false) - { - if(DayTime() == TRUE) - pGoggles = FindSunGogglesInInv( pTeamSoldier ); - else - pGoggles = FindNightGogglesInInv( pTeamSoldier ); - if(pGoggles) - { - SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); - break; - } - } - else + else if (Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus <= 0) { + // Remove nightgoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); break; } } } + // else if not wearing anything and no goggles found + else if(itemFound == false && pTeamSoldier->inv[bSlot1].exists() == false) + { + bestBonus = 0; + // search helmet and vest for goggles of some kind + for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) + { + pObj = &(pTeamSoldier->inv[gear]); + for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) + { + if(fToNightVision == FALSE && Item[iter->usItem].brightlightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) + { + pGoggles = &(*iter); + bestBonus = Item[iter->usItem].brightlightvisionrangebonus; + } + else if(fToNightVision == TRUE && Item[iter->usItem].nightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) + { + pGoggles = &(*iter); + bestBonus = Item[iter->usItem].nightvisionrangebonus; + } + } + if(pGoggles) + { + pGoggles->MoveThisObjectTo(pTeamSoldier->inv[bSlot1], 1, pTeamSoldier, bSlot1); + pObj->RemoveAttachment(pGoggles); + break; + } + } + if(pTeamSoldier->inv[bSlot1].exists() == false) + { + if(fToNightVision == FALSE) + pGoggles = FindSunGogglesInInv( pTeamSoldier ); + else + pGoggles = FindNightGogglesInInv( pTeamSoldier ); + if(pGoggles) + { + SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); + break; + } + } + else + { + break; + } + } } - - - fCharacterInfoPanelDirty = TRUE; fTeamPanelDirty = TRUE; fInterfacePanelDirty = DIRTYLEVEL2; @@ -5963,4 +6116,4 @@ void StackAndSort( BOOLEAN fRestrictToAmmo ) //HandleAllReachAbleItemsInTheSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pImpButtonText[11] ); } -} +} \ No newline at end of file diff --git a/Tactical/UI Cursors.cpp b/Tactical/UI Cursors.cpp index 1e92ffcb..d92c1da5 100644 --- a/Tactical/UI Cursors.cpp +++ b/Tactical/UI Cursors.cpp @@ -575,19 +575,123 @@ UINT8 HandleActivatedTargetCursor( SOLDIERTYPE *pSoldier, INT16 sMapPos, BOOLEAN { usCursor = ACTION_TARGETCONFIRMBURST_UICURSOR; + // HEADROCK HAM B1: Burst cursor now shows multiple CTH bars (one for each bullet) + // and also a Targetted Bodypart indicator. if(pSoldier->bDoAutofire == 0 && gGameSettings.fOptions[ TOPTION_CTH_CURSOR ]) { - //AXP 29.03.2007: Rooftop CtH fix. See below. - INT8 bTempTargetLevel = pSoldier->bTargetLevel; - pSoldier->bTargetLevel = (INT8) gsInterfaceLevel; + if (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) + { + // Burst mode only + OBJECTTYPE * pInHand; + pInHand = &(pSoldier->inv[pSoldier->ubAttackingHand]); + // Burst size + gbCtHBurstCount = GetShotsPerBurst(pInHand); + UINT8 i, saveDoBurst; + // Save original burst value (should be 1 always, before the burst has been fired, but + // this is just a failsafe + saveDoBurst = pSoldier->bDoBurst; - UINT32 uiHitChance; - uiHitChance = CalcChanceToHitGun( pSoldier, sMapPos, 0, pSoldier->bAimShotLocation ); + // For each bullet in the burst + for (i=0;ibTargetLevel; + pSoldier->bTargetLevel = (INT8) gsInterfaceLevel; - pSoldier->bTargetLevel = bTempTargetLevel; + // Calculate hit chance (using the current bDoBurst for burst-penalty calculations) + UINT32 uiHitChance; + uiHitChance = CalcChanceToHitGun( pSoldier, sMapPos, 0, pSoldier->bAimShotLocation ); + // HEADROCK HAM B2.7: CTH approximation? + if (gGameExternalOptions.fApproximateCTH) + { + uiHitChance = ChanceToHitApproximation( pSoldier, uiHitChance ); + } - gfUICtHBar = TRUE; - gbCtH = (gbCtH+uiHitChance)/2; + pSoldier->bTargetLevel = bTempTargetLevel; + + // Put result (burst CTH) into the array + gbCtH[i] = (gbCtH[i]+uiHitChance)/2; + + // Increase burst size, so that the next time we run CalcChanceToHitGun we'll get a lower + // result, based on our Burst Penalty + pSoldier->bDoBurst++; + } + // Reset original burst status + pSoldier->bDoBurst = saveDoBurst; + // Activate CTH bar display + gfUICtHBar = TRUE; + } + else // One "BASE CTH" bar, JA2 1.13 vanilla + { + //AXP 29.03.2007: Rooftop CtH fix. See below. + INT8 bTempTargetLevel = pSoldier->bTargetLevel; + pSoldier->bTargetLevel = (INT8) gsInterfaceLevel; + + UINT32 uiHitChance; + uiHitChance = CalcChanceToHitGun( pSoldier, sMapPos, 0, pSoldier->bAimShotLocation ); + // HEADROCK HAM B2.7: CTH approximation? + if (gGameExternalOptions.fApproximateCTH) + { + uiHitChance = ChanceToHitApproximation( pSoldier, uiHitChance ); + } + + pSoldier->bTargetLevel = bTempTargetLevel; + + gfUICtHBar = TRUE; + gbCtH[0] = (gbCtH[0]+uiHitChance)/2; + gbCtHBurstCount = 1; + } + } + + // HEADROCK HAM B2: Now autofire will show both its starting CTH and the CTH of the last + // bullet in the volley. + + else if(pSoldier->bDoAutofire > 0 && gGameSettings.fOptions[ TOPTION_CTH_CURSOR ]) + { + if (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) + { + gbCtHBurstCount = 1; + + //AXP 29.03.2007: Rooftop CtH fix. See below. + INT8 bTempTargetLevel = pSoldier->bTargetLevel; + pSoldier->bTargetLevel = (INT8) gsInterfaceLevel; + + UINT32 uiHitChance; + // Calculate CTH for the first bullet + uiHitChance = CalcChanceToHitGun( pSoldier, sMapPos, 0, pSoldier->bAimShotLocation ); + // HEADROCK HAM B2.7: CTH approximation? + if (gGameExternalOptions.fApproximateCTH) + { + uiHitChance = ChanceToHitApproximation( pSoldier, uiHitChance ); + } + + // CTH for the first bullet is entered into this array, and later displayed + gbCtH[0] = (gbCtH[0]+uiHitChance)/2; + + // Fool the CTH formula into thinking we're already firing the last bullet in the volley, + // by artificially altering bDoBurst (which tracks actual burst progression). The CTH formula + // calculates Auto Penalty based on bDoBurst - (Autopen * (bDoBurst-1), basically). + UINT8 saveDoBurst = pSoldier->bDoBurst; + pSoldier->bDoBurst = pSoldier->bDoAutofire; + + uiHitChance = CalcChanceToHitGun( pSoldier, sMapPos, 0, pSoldier->bAimShotLocation ); + // HEADROCK HAM B2.7: CTH approximation? + if (gGameExternalOptions.fApproximateCTH) + { + uiHitChance = ChanceToHitApproximation( pSoldier, uiHitChance ); + } + + pSoldier->bTargetLevel = bTempTargetLevel; + // Return Burst State to original value + pSoldier->bDoBurst = saveDoBurst; + + // CTH for the last bullet is entered into this array and later displayed. + gbCtH[1] = (gbCtH[1]+uiHitChance)/2; + + // Flag to tell the program to draw two CTH bars. + gbCtHAutoFire = TRUE; + gfUICtHBar = TRUE; + } } } } @@ -603,11 +707,17 @@ UINT8 HandleActivatedTargetCursor( SOLDIERTYPE *pSoldier, INT16 sMapPos, BOOLEAN UINT32 uiHitChance; uiHitChance = CalcChanceToHitGun( pSoldier, sMapPos, (INT8)(pSoldier->aiData.bShownAimTime ), pSoldier->bAimShotLocation ); + // HEADROCK HAM B2.7: CTH approximation? + if (gGameExternalOptions.fApproximateCTH) + { + uiHitChance = ChanceToHitApproximation( pSoldier, uiHitChance ); + } pSoldier->bTargetLevel = bTempTargetLevel; gfUICtHBar = TRUE; - gbCtH = (gbCtH+uiHitChance)/2; + gbCtHBurstCount = 1; + gbCtH[0] = (gbCtH[0]+uiHitChance)/2; } switchVal = pSoldier->aiData.bShownAimTime; @@ -1283,8 +1393,10 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f } } - - if ( fDisplay && !pSoldier->bDoBurst) + // HEADROCK HAM B2: Now shows targetted bodypart for ALL firing modes. Note that the external toggle is not + // taken into account yet. + // if ( fDisplay && !pSoldier->bDoAutofire ) + if ( fDisplay ) { if ( gfUIFullTargetFound ) { @@ -1296,7 +1408,14 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f wcscpy( gzLocation, TacticalStr[ CROW_HIT_LOCATION_STR ] ); - gfUIBodyHitLocation = TRUE; + if ( !pSoldier->bDoBurst ) + gfUIBodyHitLocation = TRUE; + // HEADROCK: This'll toggle whether the bodypart targetting indicator shows up in the burst/auto + // CTH cursors. + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) ) + gfUIBodyHitLocation = TRUE; + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + gfUIBodyHitLocation = TRUE; return; } @@ -1318,16 +1437,37 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f { wcscpy( gzLocation, TacticalStr[ HEAD_HIT_LOCATION_STR ] ); } - gfUIBodyHitLocation = TRUE; + if ( !pSoldier->bDoBurst ) + gfUIBodyHitLocation = TRUE; + // HEADROCK: This'll toggle whether the bodypart targetting indicator shows up in the burst/auto + // CTH cursors. + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) ) + gfUIBodyHitLocation = TRUE; + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + gfUIBodyHitLocation = TRUE; break; case AIM_SHOT_TORSO: wcscpy( gzLocation, TacticalStr[ TORSO_HIT_LOCATION_STR ] ); - gfUIBodyHitLocation = TRUE; + if ( !pSoldier->bDoBurst ) + gfUIBodyHitLocation = TRUE; + // HEADROCK: This'll toggle whether the bodypart targetting indicator shows up in the burst/auto + // CTH cursors. + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) ) + gfUIBodyHitLocation = TRUE; + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + gfUIBodyHitLocation = TRUE; break; case AIM_SHOT_LEGS: wcscpy( gzLocation, TacticalStr[ LEGS_HIT_LOCATION_STR ] ); + if ( !pSoldier->bDoBurst ) + gfUIBodyHitLocation = TRUE; + // HEADROCK: This'll toggle whether the bodypart targetting indicator shows up in the burst/auto + // CTH cursors. + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) ) + gfUIBodyHitLocation = TRUE; + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) gfUIBodyHitLocation = TRUE; break; } @@ -2384,3 +2524,80 @@ void HandleUICursorRTFeedback( SOLDIERTYPE *pSoldier ) } } +// HEADROCK HAM B2.7: This function gives us an "approximate" CTH bar, that doesn't show the exact value of +// CTH unless the character is well-trained. The worse trained a character is, the wilder the speculation. + +UINT32 ChanceToHitApproximation( SOLDIERTYPE * pSoldier, UINT32 uiChance ) +{ + UINT16 bExpLevel = pSoldier->stats.bExpLevel; + UINT16 bMarksmanship = pSoldier->stats.bMarksmanship; + UINT16 bWisdom = pSoldier->stats.bWisdom; + UINT16 iNumStages, iNumStagesBase, StageSize, SubStageSize; + UINT8 iSniper; + UINT16 uiNewChance; + UINT16 i; + + if ( pSoldier->ubProfile != NO_PROFILE ) + { + iSniper = NUM_SKILL_TRAITS( pSoldier, PROF_SNIPER ); + } + else + { + // Dunno, but I get the strong feeling that we don't want CTH bars for non-mercs. + return(0); + } + + if (uiChance == 100) + { + // Sure hit. The player needs to set Maximum_CTH to 100 for this to happen. Normally, the + // max chance is only 99. + return(100); + } + if (uiChance == 0) + { + // Sure Miss. The player needs to set Minimum_CTH to 0 for this to happen. Normally, the + // min chance is 1. + return(0); + } + + uiNewChance = uiChance; + iNumStagesBase = __min(165,__max(0, ((bExpLevel * 10) - 30) + (bMarksmanship - 25) + (bWisdom - 50))); + //iNumStagesBase = __max(255, iNumStagesBase); + + iNumStagesBase = __min(165, iNumStagesBase + (10 * iSniper)); + + iNumStagesBase = (UINT16)((float)iNumStagesBase * (float)((float)iNumStagesBase / 195.0)); + + // Gives a number between 2 and 51. + iNumStages = __max(2,(100 * iNumStagesBase) / 300); + + // Find out how large the slices are + StageSize = 100/iNumStages; + // Correct iNumStages to match StageSize, +1 compensates for the rounding down of StageSize. + iNumStages = (100/StageSize) + 1; + // These sub-stages are used to determine which way to round the results. + SubStageSize = __max(1, StageSize / 2); + + if (StageSize == 1) + // CTH assessment is perfect. No need for the rest of this function + return (uiChance); + + // Jump each "stage" and see whether the CTH is within this slice of the scale + for (i=0; i (UINT16) uiChance) ) + { + return (CurrentStage); + } + else if ( (CurrentStage + SubStageSize <= (UINT16) uiChance) && (NextStage > (UINT16) uiChance)) + { + return (NextStage); + } + } + + // Something went wrong. Return 50, as in "God Knows" + return (50); +} diff --git a/Tactical/UI Cursors.h b/Tactical/UI Cursors.h index c7b76082..cf82e75c 100644 --- a/Tactical/UI Cursors.h +++ b/Tactical/UI Cursors.h @@ -24,5 +24,8 @@ void HandleEndConfirmCursor( SOLDIERTYPE *pSoldier ); BOOLEAN GetMouseRecalcAndShowAPFlags( UINT32 *puiCursorFlags, BOOLEAN *pfShowAPs ); +// HEADROCK HAM B2.7: This function calculates the nearest value (display purposes only) +// based on how trained the shooter is. +UINT32 ChanceToHitApproximation( SOLDIERTYPE * pSoldier, UINT32 uiChance ); #endif \ No newline at end of file diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 5a9bd99f..0c9ef229 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -59,8 +59,9 @@ extern INT8 gbCurrentRainIntensity; //end rain -#define MINCHANCETOHIT 1 -#define MAXCHANCETOHIT 99 +// HEADROCK HAM B1: Externalized both values to INI +//#define MINCHANCETOHIT 1 +//#define MAXCHANCETOHIT 99 // NB this is arbitrary, chances in DG ranged from 1 in 6 to 1 in 20 #define BASIC_DEPRECIATE_CHANCE 15 @@ -105,6 +106,10 @@ INT32 HTHImpact( SOLDIERTYPE * pSoldier, SOLDIERTYPE * pTarget, INT32 iHitBy, BO BOOLEAN gfNextShotKills = FALSE; BOOLEAN gfReportHitChances = FALSE; +// HEADROCK HAM B2.5:Using this boolean to tell the CTH formula that it is being called by UseGun(), rather than +// by other functions. This is mostly to assist the new Tracer Fire system... +BOOLEAN fCalculateCTHDuringGunfire = FALSE; + //GLOBALS // TODO: Move strings to extern file @@ -1683,8 +1688,10 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo ) } else { - uiHitChance = CalcChanceToHitGun( pSoldier, sTargetGridNo, pSoldier->aiData.bAimTime, pSoldier->bAimShotLocation ); + fCalculateCTHDuringGunfire = TRUE; + uiHitChance = CalcChanceToHitGun( pSoldier, sTargetGridNo, pSoldier->aiData.bAimTime, pSoldier->bAimShotLocation ); } + fCalculateCTHDuringGunfire = FALSE; //DIGICRAB: Barrel extender wear code // Relocated from CalcChanceToHitGun @@ -1728,10 +1735,19 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo ) // big penalty to hit // WDS 07/06/2008 fix randoms //if(uiHitChance < 30) - if(uiHitChance <= 30) - uiHitChance = MINCHANCETOHIT; + // HEADROCK: Altered formula to accept variable MINIMUM CTH. + //if(uiHitChance <= 30) + // uiHitChance = MINCHANCETOHIT; + //else + // uiHitChance -= 30; + if(uiHitChance <= (UINT16)(__max(30, gGameExternalOptions.iMinimumCTH + 30))) + { + uiHitChance = gGameExternalOptions.iMinimumCTH ; + } else + { uiHitChance -= 30; + } // curse! if ( pSoldier->bTeam == OUR_TEAM ) @@ -3552,8 +3568,9 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, if ( pSoldier->stats.bMarksmanship == 0 ) { - // always min chance - return( MINCHANCETOHIT ); + // HEADROCK: (HAM) Altered to accept external arguments + // return( MINCHANCETOHIT ); + return( gGameExternalOptions.iMinimumCTH ); } // make sure the guy's actually got a weapon in his hand! @@ -3724,7 +3741,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, } // If in burst mode, deduct points for change to hit for each shot after the first - if ( pSoldier->bDoBurst > 0 && pSoldier->bDoAutofire == 0 ) + if ( pSoldier->bDoBurst && pSoldier->bDoAutofire == 0 ) { // Snap: bipod may reduce burst penalty iPenalty = GetBurstPenalty(pInHand, gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE) @@ -3749,6 +3766,54 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, iPenalty /= 2 * NUM_SKILL_TRAITS( pSoldier, AUTO_WEAPS ); } iChance -= iPenalty; + // HEADROCK HAM B2.5: One of every X bullets in a tracer magazine is a tracer round, which will + // bump the CTH up by a certain amount. + + if (AmmoTypes[(*pInHand)[0]->data.gun.ubGunAmmoType].tracerEffect == 1 && gGameExternalOptions.iRealisticTracers > 0 ) + { + UINT16 iBulletsLeft, iTracersFired, iBulletsPerTracer, iBulletsSinceLastTracer; + UINT8 cnt; + UINT16 iAutoPenaltySinceLastTracer; + iTracersFired = 0; + iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; + + // Calculate number of bullets left right before firing this bullet + if (fCalculateCTHDuringGunfire) + { + iBulletsLeft = (*pInHand)[0]->data.gun.ubGunShotsLeft + (pSoldier->bDoBurst - 1); + } + else + { + iBulletsLeft = (*pInHand)[0]->data.gun.ubGunShotsLeft; + } + + iBulletsSinceLastTracer = 0; + for (cnt=0;cntbDoBurst;cnt++) + { + iBulletsSinceLastTracer++; + if ((( iBulletsLeft - (cnt - 1) ) / iBulletsPerTracer) - ((iBulletsLeft - cnt) / iBulletsPerTracer) == 1) + { + iBulletsSinceLastTracer = 0; + } + } + + iTracersFired = ((iBulletsLeft+1) / iBulletsPerTracer) - (((iBulletsLeft+1) - (pSoldier->bDoBurst)) / iBulletsPerTracer); + + if ( iTracersFired > 0 ) + { + // Correct all autofire penalty so far + iBonus = iPenalty; + // Add Tracer Bump if previous bullet was a tracer + //if (iBulletsSinceLastTracer == 0) + iBonus += (gGameExternalOptions.iCTHBumpPerTracer * iTracersFired); + // Calculate penalty since last tracer was fired + iAutoPenaltySinceLastTracer = GetAutoPenalty(pInHand, gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE) * iBulletsSinceLastTracer; + // Add penalty to bonus. + iBonus -= iAutoPenaltySinceLastTracer; + + iChance += iBonus; + } + } } //ADB we need to calculate the distance visible and SoldierTo...LOSTests that we want to @@ -3866,7 +3931,22 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, // if shooter is still in shock if (pSoldier->aiData.bShock) - iChance -= (pSoldier->aiData.bShock * AIM_PENALTY_PER_SHOCK); + { + // HEADROCK HAM B2.8: Placed a maximum here, as shock is now also used in suppression. + UINT16 usShockPenalty; + + usShockPenalty = pSoldier->aiData.bShock * AIM_PENALTY_PER_SHOCK; + + if (gGameExternalOptions.usMaxShooterCoweringPenalty > 0) + { + if ( usShockPenalty > gGameExternalOptions.usMaxShooterCoweringPenalty ) + usShockPenalty = gGameExternalOptions.usMaxShooterCoweringPenalty; + } + if ( usShockPenalty < 1 ) + usShockPenalty = 1; + + iChance -= usShockPenalty; + } // WANNE: Changed this, because RPGs are not in the calculation, only guns //if ( Item[ usInHand ].usItemClass == IC_GUN ) @@ -3962,6 +4042,92 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, { iSightRange = 1; } + // HEADROCK HAM B2.1 : The TARGET's shock now also affects aiming. If the target is prone + // and in shock, they are harder to hit! This represents a target that's cowering as close + // to the ground (and as close to any possible cover, like a small dune or a fold of earth + // or anything like that). + if ( gGameExternalOptions.iAimPenaltyPerTargetShock > 0 ) + { + // HEADROCK HAM B2.1 : This value determines how much penalty the target's shock-value gives the shooter. + // As of HAM B2.3: There's a maximum range at which 100% penalty is given. + // As of HAM B2.8: Target's stance and the targetted bodypart will affect the end result. + + INT8 AIM_PENALTY_PER_TARGET_SHOCK; + INT16 sCoweringPenalty = 0; + UINT8 ubCoweringDivisor; + UINT16 MIN_RANGE_FOR_FULL_COWER; + UINT16 MAX_TARGET_COWERING_PENALTY; + + AIM_PENALTY_PER_TARGET_SHOCK = gGameExternalOptions.iAimPenaltyPerTargetShock; + MIN_RANGE_FOR_FULL_COWER = gGameExternalOptions.usMinRangeForFullCoweringPenalty; + MAX_TARGET_COWERING_PENALTY = gGameExternalOptions.usMaxTargetCoweringPenalty; + + pTarget = SimpleFindSoldier( sGridNo, pSoldier->bTargetLevel ); + if (pTarget != NULL) + { + if (pTarget->aiData.bShock ) + { + if (gAnimControl[ pTarget->usAnimState ].ubHeight == ANIM_PRONE) + { + ubCoweringDivisor = gGameExternalOptions.ubCoweringPenaltyDivisorProne; + + sCoweringPenalty = (pTarget->aiData.bShock * AIM_PENALTY_PER_TARGET_SHOCK); + sCoweringPenalty = sCoweringPenalty / ubCoweringDivisor; + sCoweringPenalty = (sCoweringPenalty * __min(iSightRange,MIN_RANGE_FOR_FULL_COWER)) / MIN_RANGE_FOR_FULL_COWER; + + if (gGameExternalOptions.usMaxTargetCoweringPenalty > 0) + { + if ( sCoweringPenalty > gGameExternalOptions.usMaxTargetCoweringPenalty ) + sCoweringPenalty = gGameExternalOptions.usMaxTargetCoweringPenalty; + } + if ( sCoweringPenalty < 1 ) + sCoweringPenalty = 1; + + iChance -= sCoweringPenalty; + } + else if (gAnimControl[ pTarget->usAnimState ].ubHeight == ANIM_CROUCH) + { + switch ( ubAimPos ) + { + case AIM_SHOT_HEAD: + ubCoweringDivisor = gGameExternalOptions.ubCoweringPenaltyDivisorCrouchedHead; + break; + case AIM_SHOT_TORSO: + case AIM_SHOT_RANDOM: + case AIM_SHOT_GLAND: + ubCoweringDivisor = gGameExternalOptions.ubCoweringPenaltyDivisorCrouchedTorso; + break; + case AIM_SHOT_LEGS: + ubCoweringDivisor = gGameExternalOptions.ubCoweringPenaltyDivisorCrouchedLegs; + break; + } + + sCoweringPenalty = (pTarget->aiData.bShock * AIM_PENALTY_PER_TARGET_SHOCK); + sCoweringPenalty = sCoweringPenalty / ubCoweringDivisor; + sCoweringPenalty = (sCoweringPenalty * __min(iSightRange,MIN_RANGE_FOR_FULL_COWER)) / MIN_RANGE_FOR_FULL_COWER; + + if (gGameExternalOptions.usMaxTargetCoweringPenalty > 0) + { + if ( sCoweringPenalty > gGameExternalOptions.usMaxTargetCoweringPenalty ) + sCoweringPenalty = gGameExternalOptions.usMaxTargetCoweringPenalty; + } + if ( sCoweringPenalty < 1 ) + sCoweringPenalty = 1; + + // HEADROCK HAM B2.8.1: Added formula to make sure that cowering target penalties + // are not given when on the roof. + + if (pSoldier->pathing.bLevel == pTarget->pathing.bLevel && pSoldier->pathing.bLevel > 0) + sCoweringPenalty = 0; // No cowering penalties when both are on the roof! + else if (pSoldier->pathing.bLevel < pSoldier->pathing.bLevel && gAnimControl[ pTarget->usAnimState ].ubHeight == ANIM_PRONE) + sCoweringPenalty *= 2; // Much harder to shoot at anyone cowering above you. + else if (pSoldier->pathing.bLevel > pSoldier->pathing.bLevel) + sCoweringPenalty /= 2; // Much easier to shoot at cowerers below you. + iChance -= sCoweringPenalty; + } + } + } + } DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: after scope bonus - ubAimTime = %d, iSightRange = %d, iChance = %d ", ubAimTime, iSightRange, iChance)); UINT8 bLightLevel = LightTrueLevel(sGridNo, pSoldier->bTargetLevel); @@ -4123,7 +4289,8 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, } // penalty for amount that enemy has moved - iPenalty = __min( ((pTarget->bTilesMoved * 3) / 2), 30 ); + // HEADROCK HAM B2.6: Externalized the value + iPenalty = __min( (UINT16)((float)pTarget->bTilesMoved * (float)gGameExternalOptions.iMovementEffectOnAiming), 30 ); iChance -= iPenalty; // if target sees us, he may have a chance to dodge before the gun goes off @@ -4235,7 +4402,12 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, } // MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS - if (iChance < MINCHANCETOHIT) + // HEADROCK: (HAM) Altered so called "Defined Limits" to accept external arguments. + // The divisor argument only works when the minimum is set to 0. It has a chance of 1 in X to + // bump the minimum back to 1, where X = the Divisor value. So a divisor value of 50 gives a 1/50 + // chance of getting some actual chance to hit despite the 0 minimum. The overall total would then + // be an effective CTH of only 1/5000 (50 chances to get a 1 out of 100 CTH, hehehe) + if (iChance <= gGameExternalOptions.iMinimumCTH) { if ( TANK( pSoldier ) ) { @@ -4244,14 +4416,24 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, } else { - iChance = MINCHANCETOHIT; + iChance = gGameExternalOptions.iMinimumCTH; + if ( gGameExternalOptions.iMinimumCTH == 0 ) + { + if ( PreRandom( gGameExternalOptions.iMinimumCTHDivisor ) == (gGameExternalOptions.iMinimumCTHDivisor - 1) ) + { + iChance = 1; + } + } } } - else - { - if (iChance > MAXCHANCETOHIT) - iChance = MAXCHANCETOHIT; - } + else + { + // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI + // if (iChance > MAXCHANCETOHIT) + // iChance = MAXCHANCETOHIT; + if (iChance > gGameExternalOptions.iMaximumCTH) + iChance = gGameExternalOptions.iMaximumCTH; + } // NumMessage("ChanceToHit = ",chance); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun: ichance = %d",iChance)); @@ -5305,14 +5487,19 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi // MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS - if (iChance < MINCHANCETOHIT) + // HEADROCK: I urinate on your Defined Limits! Power Rangers, Externalize! + // Disclaimer: No offense meant, all in good fun ;) + if (iChance < gGameExternalOptions.iMinimumCTH) { - iChance = MINCHANCETOHIT; + iChance = gGameExternalOptions.iMinimumCTH; } else { - if (iChance > MAXCHANCETOHIT) - iChance = MAXCHANCETOHIT; + // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI + //if (iChance > MAXCHANCETOHIT) + // iChance = MAXCHANCETOHIT; + if (iChance > gGameExternalOptions.iMaximumCTH) + iChance = gGameExternalOptions.iMaximumCTH; } //NumMessage("ChanceToStab = ",chance); @@ -5768,13 +5955,17 @@ UINT32 CalcThrownChanceToHit(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTi // reduce iChance to hit DIRECTLY by the item's working condition iChance = (iChance * WEAPON_STATUS_MOD(pSoldier->inv[HANDPOS][0]->data.objectStatus)) / 100; - // MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS - if (iChance < MINCHANCETOHIT) - iChance = MINCHANCETOHIT; + // What's with all these defined limits? Let's think out of the box for a minute, shall we? + // HEADROCK (HAM): externalized, effective immediately. + if (iChance < gGameExternalOptions.iMinimumCTH) + iChance = gGameExternalOptions.iMinimumCTH; else { - if (iChance > MAXCHANCETOHIT) - iChance = MAXCHANCETOHIT; + // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI + //if (iChance > MAXCHANCETOHIT) + // iChance = MAXCHANCETOHIT; + if (iChance > gGameExternalOptions.iMaximumCTH) + iChance = gGameExternalOptions.iMaximumCTH; } @@ -5969,7 +6160,13 @@ UINT8 GetAutofireShotsPerFiveAPs( OBJECTTYPE *pObj ) { // DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("GetAutofireShotsPerFiveAPs")); - return Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP; +// HEADROCK HAM B2.6: Added overall modifier + if (Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP > 0) + { + return __max((Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP + gGameExternalOptions.iAutofireBulletsPer5APModifier), 0); + } + else + return 0; } UINT16 GetMagSize( OBJECTTYPE *pObj ) diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h index 3c04847a..4f57af87 100644 --- a/Tactical/Weapons.h +++ b/Tactical/Weapons.h @@ -19,7 +19,8 @@ enum WeaponMode void HandleTacticalEffectsOfEquipmentChange( SOLDIERTYPE *pSoldier, UINT32 uiInvPos, UINT16 usOldItem, UINT16 usNewItem ); -#define MAXCHANCETOHIT 99 +// HEADROCK: Removed this and externalized to JA2_OPTIONS.INI as part of HAM project. +//#define MAXCHANCETOHIT 99 #define BAD_DODGE_POSITION_PENALTY 20 #define GUN_BARREL_RANGE_BONUS 100 diff --git a/Tactical/bullets.cpp b/Tactical/bullets.cpp index 399dde9c..be9da9ba 100644 --- a/Tactical/bullets.cpp +++ b/Tactical/bullets.cpp @@ -470,7 +470,11 @@ void AddMissileTrail( BULLET *pBullet, FIXEDPT qCurrX, FIXEDPT qCurrY, FIXEDPT q } } - if (fTracer == TRUE) + // HEADROCK HAM B2.5: Created new bullet flag that tells us whether this specific bullet is a tracer. + // The condition now reads that flag and creates a lightshow only for tracer bullets. This flag is only + // used if the new Tracer System is on. + //if (fTracer == TRUE) + if ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE)) { if ( pBullet->iLoop < 5 ) { @@ -517,7 +521,11 @@ void AddMissileTrail( BULLET *pBullet, FIXEDPT qCurrX, FIXEDPT qCurrY, FIXEDPT q AniParams.sDelay = (INT16)( 100 ); } //else if ( pBullet->usFlags & ( BULLET_FLAG_TRACER ) ) - else if (fTracer == TRUE) + // HEADROCK HAM B2.5: Created new bullet flag that tells us whether this specific bullet is a tracer. + // The condition now reads that flag and creates a lightshow only for tracer bullets. This flag is only + // used if the new Tracer System is on. + // else if (fTracer == TRUE) + else if ((gGameExternalOptions.iRealisticTracers > 0 && gGameExternalOptions.iNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.iRealisticTracers == 0 && fTracer == TRUE)) { INT16 sXPos, sYPos; diff --git a/TacticalAI/AIInternals.h b/TacticalAI/AIInternals.h index f097f8f1..0fe5decb 100644 --- a/TacticalAI/AIInternals.h +++ b/TacticalAI/AIInternals.h @@ -231,4 +231,8 @@ INT16 FindBestCoverNearTheGridNo(SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubS INT8 FindDirectionForClimbing( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bLevel); +// HEADROCK HAM B2.7: Functions to assist group AI +INT16 AssessTacticalSituation( INT8 bSide ); +BOOLEAN TeamSeesOpponent( INT8 bSide, SOLDIERTYPE * pOpponent ); +INT32 CalcStraightThreatValue( SOLDIERTYPE *pEnemy ); diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index 889c9d4d..98f98cf7 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -2565,3 +2565,236 @@ void AINameMessage(SOLDIERTYPE * pSoldier,const STR8 str,INT32 num) DebugAI( tempstr ); } #endif +///////////////////////////////////////////////////////////////////////////////////////////////// +// HEADROCK: +// +// The following function(s) are part of my half-assed attempt to have the AI analyze the tactical +// situation, by comparing (known) squad sizes, the state of all combatants, and the orders of all +// friendlies. The idea is to return a value called "TacticalSituation" which can tell a combatant +// whether he should try to undertake a smarter course of action. +///////////////////////////////////////////////////////////////////////////////////////////////// + +INT16 AssessTacticalSituation( INT8 bTeam ) +{ + UINT16 ubFriendlyTeamTacticalValue = 0; + UINT16 ubEnemyTeamTacticalValue = 0; + UINT8 ubSoldierTacticalThreat; + INT16 ubTacticalSituation; + UINT16 cnt; + SOLDIERTYPE * pSoldier; + + // begin loop through all MERCs. + for ( cnt = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; cnt <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; cnt++ ) + { + pSoldier = MercPtrs[ cnt ]; + ubSoldierTacticalThreat = CalcStraightThreatValue( pSoldier ); + // Player-controlled Mercs are 1.5 times more threatening than AIs + if (pSoldier->flags.uiStatusFlags & SOLDIER_PC) + ubSoldierTacticalThreat = (UINT8)((float)ubSoldierTacticalThreat * 1.5); + + // Assess Threat + if (bTeam == OUR_TEAM || bTeam == MILITIA_TEAM) + { + // Friendly! + ubFriendlyTeamTacticalValue += ubSoldierTacticalThreat; + } + else + { + // Enemy! + if ( TeamSeesOpponent( ENEMY_TEAM, pSoldier ) ) + ubEnemyTeamTacticalValue += ubSoldierTacticalThreat; + + } + } + + // begin loop through all Militia. + for ( cnt = gTacticalStatus.Team[ MILITIA_TEAM ].bFirstID; cnt <= gTacticalStatus.Team[ MILITIA_TEAM ].bLastID; cnt++ ) + { + pSoldier = MercPtrs[ cnt ]; + ubSoldierTacticalThreat = CalcStraightThreatValue( pSoldier ); + + // Assess Threat + if (bTeam == OUR_TEAM || bTeam == MILITIA_TEAM) + { + // Friendly! + ubFriendlyTeamTacticalValue += ubSoldierTacticalThreat; + } + else + { + // Enemy! + if ( TeamSeesOpponent( ENEMY_TEAM, pSoldier ) ) + ubEnemyTeamTacticalValue += ubSoldierTacticalThreat; + } + } + + // begin loop through all Enemies. + for ( cnt = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; cnt <= gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; cnt++ ) + { + pSoldier = MercPtrs[ cnt ]; + ubSoldierTacticalThreat = CalcStraightThreatValue( pSoldier ); + + // Assess Threat + + if (bTeam == ENEMY_TEAM) + { + // Friendly! + ubFriendlyTeamTacticalValue += ubSoldierTacticalThreat; + } + else + { + // Enemy! + if ( TeamSeesOpponent( OUR_TEAM, pSoldier ) || TeamSeesOpponent ( MILITIA_TEAM, pSoldier) ) + ubEnemyTeamTacticalValue += ubSoldierTacticalThreat; + } + + } + + ubTacticalSituation = ubEnemyTeamTacticalValue - ubFriendlyTeamTacticalValue; + + return (ubTacticalSituation); + + +} + + +// HEADROCK: Function to check whether a team can see the specified soldier. +BOOLEAN TeamSeesOpponent( INT8 bTeam, SOLDIERTYPE * pOpponent ) +{ + SOLDIERTYPE * pSoldier; + UINT16 cnt; + + // This assertion can be safely removed, assuming the program does what it should. It simply checks + // whether the "opponent" is on the same team being checked. That should be avoided when calling this + // function. + Assert( pOpponent->bTeam != bTeam ); + + // We're checking Merc/Militia visibility + if (bTeam == OUR_TEAM || bTeam == MILITIA_TEAM ) + { + for ( cnt = gTacticalStatus.Team[ MILITIA_TEAM ].bFirstID; cnt <= gTacticalStatus.Team[ MILITIA_TEAM ].bLastID; cnt++ ) + { + pSoldier = MercPtrs[ cnt ]; + + if (pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife >= OKLIFE) + { + + + if (pSoldier->aiData.bOppList[ pOpponent->ubID ] == SEEN_CURRENTLY) + return ( TRUE ); + } + } + for ( cnt = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; cnt <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; cnt++ ) + { + pSoldier = MercPtrs[ cnt ]; + + if (pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife >= OKLIFE) + { + // This assertion can be safely removed, assuming the program does what it should. It simply checks + // whether the "opponent" is on the same team being checked. That should be avoided when calling this + // function. + //Assert( pOpponent->bSide != bSide ); + + if (pSoldier->aiData.bOppList[ pOpponent->ubID ] == SEEN_CURRENTLY) + return ( TRUE ); + } + } + + return ( FALSE ); + } + // Check enemy visibility + else if (bTeam == ENEMY_TEAM) + { + for ( cnt = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; cnt <= gTacticalStatus.Team[ ENEMY_TEAM ].bLastID; cnt++ ) + { + pSoldier = MercPtrs[ cnt ]; + + if (pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife >= OKLIFE) + { + // This assertion can be safely removed, assuming the program does what it should. It simply checks + // whether the "opponent" is on the same team being checked. That should be avoided when calling this + // function. + //Assert( pOpponent->bSide != bSide ); + + if (pSoldier->aiData.bOppList[ pOpponent->ubID ] == SEEN_CURRENTLY) + return ( TRUE ); + } + } + return ( FALSE ); + } + + else + return (FALSE); + +} + +// HEADROCK: Function to assess an enemy's threat value without "me" argument. +INT32 CalcStraightThreatValue( SOLDIERTYPE *pEnemy ) +{ + INT32 iThreatValue = 0; + + // If man is inactive, at base, on assignment, dead, unconscious + if (!pEnemy->bActive || !pEnemy->bInSector || !pEnemy->stats.bLife ) + { + // he's no threat at all, return a negative number + iThreatValue = 0; + return(iThreatValue); + } + + else + { + // ADD twice the man's level (2-20) + iThreatValue += pEnemy->stats.bExpLevel; + + // ADD man's total action points (10-35) + iThreatValue += pEnemy->CalcActionPoints(); + + // ADD 1/2 of man's current action points (4-17) + iThreatValue += (pEnemy->bActionPoints / 2); + + // ADD 1/10 of man's current health (0-10) + iThreatValue += (pEnemy->stats.bLife / 10); + + if (pEnemy->bAssignment < ON_DUTY ) + { + // ADD 1/4 of man's protection percentage (0-25) + iThreatValue += ArmourPercent( pEnemy ) / 4; + + // ADD 1/5 of man's marksmanship skill (0-20) + iThreatValue += (pEnemy->stats.bMarksmanship / 5); + + if ( Item[ pEnemy->inv[HANDPOS].usItem ].usItemClass & IC_WEAPON ) + { + // ADD the deadliness of the item(weapon) he's holding (0-50) + iThreatValue += Weapon[pEnemy->inv[HANDPOS].usItem].ubDeadliness; + } + } + + // SUBTRACT 1/5 of man's bleeding (0-20) + iThreatValue -= (pEnemy->bBleeding / 5); + + // SUBTRACT 1/10 of man's breath deficiency (0-10) + iThreatValue -= ((100 - pEnemy->bBreath) / 10); + + // SUBTRACT man's current shock value + iThreatValue -= pEnemy->aiData.bShock; + } + + // if this man is conscious + if (pEnemy->stats.bLife < OKLIFE) + { + // if he's still something of a threat + if (iThreatValue > 0) + { + // drastically reduce his threat value (divide by 5 to 18) + iThreatValue /= (4 + (OKLIFE - pEnemy->stats.bLife)); + } + } + + // threat value of any opponent can never drop below 1 + if (iThreatValue < 0) + { + iThreatValue = 0; + } + + return(iThreatValue); +} diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index dff31d3a..c7a874f8 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -1296,7 +1296,9 @@ void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAt } } else - ubChanceToHit = MAXCHANCETOHIT; + // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI + // ubChanceToHit = MAXCHANCETOHIT; + ubChanceToHit = gGameExternalOptions.iMaximumCTH; //NumMessage("chance to Hit = ",ubChanceToHit); //sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit); @@ -1473,7 +1475,9 @@ void CalcTentacleAttack(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab ) ubChanceToHit = (INT16) CalcChanceToStab(pSoldier,pOpponent,ubAimTime); } else - ubChanceToHit = MAXCHANCETOHIT; + // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI + // ubChanceToHit = MAXCHANCETOHIT; + ubChanceToHit = gGameExternalOptions.iMaximumCTH; //NumMessage("chance to Hit = ",ubChanceToHit); //sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit); @@ -2472,8 +2476,20 @@ void CheckIfShotPossible(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN s } //if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > pSoldier->MaxDistanceVisible(pBestShot->sTarget, pBestShot->bTargetLevel) ) || pSoldier->bOrders == SNIPER ) ) || - if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxNormalDistanceVisible() ) || pSoldier->aiData.bOrders == SNIPER ) ) || - (suppressionFire && IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn] ) && GetMagSize(pObj) > 30 && (*pObj)[0]->data.gun.ubGunShotsLeft > 20 )) + // HEADROCK HAM B2.4: Changed this again - weapons are no longer checked for larger magazine to allow suppressive fire, due to + // suppressive fire revamp. +// if ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxNormalDistanceVisible() ) || pSoldier->aiData.bOrders == SNIPER ) ) || +// (suppressionFire && IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn] ) && GetMagSize(pObj) > 30 && (*pObj)[0]->data.gun.ubGunShotsLeft > 20 )) + BOOLEAN fEnableAISuppression = FALSE; + + if ( gGameExternalOptions.fIncreaseAISuppressionFire && ((!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxNormalDistanceVisible() ) || pSoldier->aiData.bOrders == SNIPER ) ) || + (suppressionFire && IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn]))) ) + fEnableAISuppression = TRUE; + else if ( !gGameExternalOptions.fIncreaseAISuppressionFire && ( (!suppressionFire && ( (IsScoped(pObj) && GunRange(pObj) > MaxNormalDistanceVisible() ) || pSoldier->aiData.bOrders == SNIPER ) ) || + (suppressionFire && IsGunAutofireCapable(&pSoldier->inv[pBestShot->bWeaponIn] ) && GetMagSize(pObj) > 30 && (*pObj)[0]->data.gun.ubGunShotsLeft > 20 ))) + fEnableAISuppression = TRUE; + + if (fEnableAISuppression) { // get the minimum cost to attack with this item DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"CheckIfShotPossible: getting min aps"); diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 7dd5a57c..3a2dc6f5 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -1950,6 +1950,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) BOOLEAN fCivilian = (PTR_CIVILIAN && (pSoldier->ubCivilianGroup == NON_CIV_GROUP || (pSoldier->aiData.bNeutral && gTacticalStatus.fCivGroupHostile[pSoldier->ubCivilianGroup] == CIV_GROUP_NEUTRAL) || (pSoldier->ubBodyType >= FATCIV && pSoldier->ubBodyType <= CRIPPLECIV) ) ); + // HEADROCK HAM B2.7: Calculate the overall tactical situation + INT16 ubOverallTacticalSituation = AssessTacticalSituation(pSoldier->bSide); DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: soldier orders = %d",pSoldier->aiData.bOrders)); @@ -2313,7 +2315,17 @@ if(!is_networked)//hayden CheckIfShotPossible(pSoldier,&BestShot,TRUE); //must have a small chance to hit and the opponent must be on the ground (can't suppress guys on the roof) - if ( BestShot.ubPossible && BestShot.ubChanceToReallyHit < 50 && Menptr[BestShot.ubOpponent].pathing.bLevel == 0 && pSoldier->aiData.bOrders != SNIPER ) + // HEADROCK HAM BETA2.4: Adjusted this for a random chance to suppress regardless of chance. This augments + // current revamp of suppression fire. + BOOLEAN fEnableAIAutofire = FALSE; + + if ( gGameExternalOptions.fIncreaseAISuppressionFire && ( ( BestShot.ubPossible && BestShot.ubChanceToReallyHit < 50 ) || (BestShot.ubPossible && BestShot.ubChanceToReallyHit < PreRandom(100)) && Menptr[BestShot.ubOpponent].pathing.bLevel == 0 && pSoldier->aiData.bOrders != SNIPER )) + fEnableAIAutofire = TRUE; + + else if ( !gGameExternalOptions.fIncreaseAISuppressionFire && ( BestShot.ubPossible && BestShot.ubChanceToReallyHit < 50 && Menptr[BestShot.ubOpponent].pathing.bLevel == 0 && pSoldier->aiData.bOrders != SNIPER )) + fEnableAIAutofire = TRUE; + + if (fEnableAIAutofire) { // then do it! @@ -3141,7 +3153,23 @@ if(!is_networked)//hayden { // only try to run if we've actually been hit recently & noticably so // otherwise, presumably our current cover is pretty good & sufficient - if (pSoldier->aiData.bShock > 0 || fCivilian) + // HEADROCK HAM B2.6: New value here helps us change the ratio of running away due to shock. This + // is terribly important if Suppression Shock is enabled. + UINT16 bShock = 0; + + if (gGameExternalOptions.fSuppressionShock) + { + // If bShock value is greater than (2*ExpLevel + MoraleModifier)*1.5, the target will flee. + bShock = pSoldier->aiData.bShock; + if (bShock <= ((float)CalcSuppressionTolerance(pSoldier)*(float)1.5)) + bShock = 0; + } + else + { + bShock = pSoldier->aiData.bShock; + } + + if (bShock > 0 || fCivilian) { // look for best place to RUN AWAY to (farthest from the closest threat) pSoldier->aiData.usActionData = FindSpotMaxDistFromOpponents(pSoldier); @@ -4424,7 +4452,10 @@ INT8 DecideActionBlack(SOLDIERTYPE *pSoldier) case ATTACKSLAYONLY:iChance += 30; break; } - if ( pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 50 ) + // HEADROCK HAM B2.6: Allows control over increased enemy burstfire. + if ( gGameExternalOptions.fIncreaseAISuppressionFire && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 10 ) + iChance += 20; + else if ( !gGameExternalOptions.fIncreaseAISuppressionFire && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 50 ) iChance += 20; // increase chance based on proximity and difficulty of enemy @@ -4502,7 +4533,10 @@ INT8 DecideActionBlack(SOLDIERTYPE *pSoldier) } - if ( pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 50 ) + // HEADROCK HAM B2.6: Allows control over increased enemy autofire. + if ( gGameExternalOptions.fIncreaseAISuppressionFire && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 20 ) + iChance += 30; + else if ( !gGameExternalOptions.fIncreaseAISuppressionFire && pSoldier->inv[BestAttack.bWeaponIn][0]->data.gun.ubGunShotsLeft > 50 ) iChance += 30; if ( bInGas ) diff --git a/Utils/Cursors.cpp b/Utils/Cursors.cpp index f8c67a9a..1c087b3f 100644 --- a/Utils/Cursors.cpp +++ b/Utils/Cursors.cpp @@ -16,6 +16,8 @@ #include "overhead.h" #include "Cursor Control.h" #include "Sound Control.h" + // HEADROCK HAM B2.6: included this here to allow toggling the CTH bars. + #include "GameSettings.h" #endif //aim @@ -1244,10 +1246,11 @@ extern UINT16 gsCurMouseWidth;*/ void DrawMouseGraphics( ) { + // HEADROCK (HAM): Made several changes here to allow multi-shot CtH display for bursts. UINT16 * ptrBuf; UINT32 uiPitch; - UINT32 cnt; - UINT32 actualPct = __min(gbCtH,99); + UINT32 cnt, i; + UINT32 actualPct = __min(gbCtH[0],99); UINT16 usCBorderTop = Get16BPPColor( FROMRGB( 155, 155, 155 ) ); UINT16 usCBorderBottom = Get16BPPColor( FROMRGB( 120, 120, 120 ) ); UINT16 usCBar = Get16BPPColor( FROMRGB( 255, 255-255*actualPct/99, 0 ) ); @@ -1255,40 +1258,67 @@ void DrawMouseGraphics( ) UINT16 usCBar2 = Get16BPPColor( FROMRGB( 180, 140-140*actualPct/99, 0 ) ); UINT16 usCBack2 = Get16BPPColor( FROMRGB( 110, 100-100*actualPct/99, 0 ) ); UINT32 barLength = __min(35,gsCurMouseWidth); - UINT32 barY = gsCurMouseOffsetY-__min(35,gsCurMouseHeight)/2; + //UINT32 barY = gsCurMouseOffsetY-__min(35,gsCurMouseHeight)/2; + UINT32 barY; if(gfUICtHBar) { - ptrBuf = (UINT16 *) LockMouseBuffer( &uiPitch ); - uiPitch >>= 1; + // HEADROCK HAM B1/2/2.6: + // This causes the function to display two CTH bars for autofire - the CTH of the first bullet, + // and the CTH of the last bullet in the volley, stored in gbCtH[0] and [1] respectively. + if ( gbCtHAutoFire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + gbCtHBurstCount = 2; + else if ( gbCtHAutoFire ) + gbCtHBurstCount = 0; + - for(cnt = gsCurMouseOffsetX+barLength/2;cnt > gsCurMouseOffsetX-barLength/2+1;cnt--) + // Sets the initial offsets of the bars. Burst and Autofire will display them higher above the + // cursor, to avoid obscuring the target or other data. + if (gbCtHBurstCount > 1 && !gbCtHAutoFire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) ) + barY = gsCurMouseOffsetY-__min(55,gsCurMouseHeight)/2; + else if (gbCtHBurstCount && gbCtHAutoFire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + barY = gsCurMouseOffsetY-__min(55,gsCurMouseHeight)/2; + else + barY = gsCurMouseOffsetY-__min(35,gsCurMouseHeight)/2; + + for (i=0; i>= 1; + + for(cnt = gsCurMouseOffsetX+barLength/2;cnt > gsCurMouseOffsetX-barLength/2+1;cnt--) + { + ptrBuf[cnt-1 + uiPitch*(3+barY)] = usCBorderBottom; + ptrBuf[cnt-1 + uiPitch*barY] = usCBorderTop; + } + + ptrBuf[gsCurMouseOffsetX+barLength/2 + uiPitch*(1+barY)] = usCBorderBottom; + ptrBuf[gsCurMouseOffsetX-barLength/2 + uiPitch*(1+barY)] = usCBorderTop; + + ptrBuf[gsCurMouseOffsetX+barLength/2 + uiPitch*(2+barY)] = usCBorderBottom; + ptrBuf[gsCurMouseOffsetX-barLength/2 + uiPitch*(2+barY)] = usCBorderTop; + + + for(cnt = 0;cnt < (barLength-2)*actualPct/99;cnt++) + { + ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+2)] = usCBar2; + ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+1)] = usCBar; + } + + for(cnt = (barLength-2)*actualPct/99;cnt < (barLength-2);cnt++) + { + ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+2)] = usCBack2; + ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+1)] = usCBack; + } + + + UnlockMouseBuffer(); + if (gbCtHBurstCount>1) + barY = barY+5; } - - ptrBuf[gsCurMouseOffsetX+barLength/2 + uiPitch*(1+barY)] = usCBorderBottom; - ptrBuf[gsCurMouseOffsetX-barLength/2 + uiPitch*(1+barY)] = usCBorderTop; - - ptrBuf[gsCurMouseOffsetX+barLength/2 + uiPitch*(2+barY)] = usCBorderBottom; - ptrBuf[gsCurMouseOffsetX-barLength/2 + uiPitch*(2+barY)] = usCBorderTop; - - - for(cnt = 0;cnt < (barLength-2)*actualPct/99;cnt++) - { - ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+2)] = usCBar2; - ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+1)] = usCBar; - } - - for(cnt = (barLength-2)*actualPct/99;cnt < (barLength-2);cnt++) - { - ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+2)] = usCBack2; - ptrBuf[cnt + gsCurMouseOffsetX-barLength/2+1 + uiPitch*(barY+1)] = usCBack; - } - - - UnlockMouseBuffer(); + barY = gsCurMouseOffsetY-__min(35,gsCurMouseHeight)/2; } } @@ -1346,11 +1376,23 @@ void DrawMouseText( ) swprintf( pStr, L"%d", gsBulletCount ); FindFontCenterCoordinates( 0, 0, gsCurMouseWidth, gsCurMouseHeight, pStr, TINYFONT1, &sX, &sY ); - mprintf( sX, sY - 10 - GetFontHeight(TINYFONT1), pStr ); + // HEADROCK HAM B2: Moved bullet counter to right side (and a bit upwards) to accomodate two CTH bars. + //mprintf( sX, sY - 10 - GetFontHeight(TINYFONT1), pStr ); + if (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) + // New CTH Bar moves bullet count to new location. + mprintf( 46, sY - 15 - GetFontHeight(TINYFONT1), pStr ); + else + // Vanilla 1.13 + mprintf( sX, sY - 10 - GetFontHeight(TINYFONT1), pStr ); - swprintf( pStr, L"%d", gsTotalBulletCount ); - FindFontCenterCoordinates( 0, 0, gsCurMouseWidth, gsCurMouseHeight, pStr, TINYFONT1, &sX, &sY ); - mprintf( sX, sY + 7 + GetFontHeight(TINYFONT1), pStr ); + // HEADROCK HAM B2: Removed this. The bottom line will now display bodypart targetting, like Burst and + // Single. + if ( gGameExternalOptions.iNewCTHBars != 1 && gGameExternalOptions.iNewCTHBars != 3 ) + { + swprintf( pStr, L"%d", gsTotalBulletCount ); + FindFontCenterCoordinates( 0, 0, gsCurMouseWidth, gsCurMouseHeight, pStr, TINYFONT1, &sX, &sY ); + mprintf( sX, sY + 7 + GetFontHeight(TINYFONT1), pStr ); + } // reset SetFontDestBuffer( FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); diff --git a/Utils/Text.h b/Utils/Text.h index 7d78b76b..8a9707cc 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -1653,6 +1653,8 @@ extern STR16 New113Message[]; extern STR16 New113MERCMercMailTexts[]; extern STR16 MissingIMPSkillsDescriptions[]; +// HEADROCK: HAM Messages +extern STR16 New113HAMMessage[]; enum { MSG113_STORM_STARTED, diff --git a/Utils/_ChineseText.cpp b/Utils/_ChineseText.cpp index 1ff659cf..ad10fbd7 100644 --- a/Utils/_ChineseText.cpp +++ b/Utils/_ChineseText.cpp @@ -5091,9 +5091,10 @@ STR16 sRepairsDoneString[] = L"%s修复了自己的物品。", L"%s修复了所有人的枪和护甲。", L"%s修复了所有人的装备。", - L"%s修复了所有人的携带物品。", - L"%s修复了所有人的携带物品。", - L"%s修复了所有人的携带物品。", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -5406,6 +5407,11 @@ STR16 New113Message[] = L"你得检查一下你的眼部装备", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the ?characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_DutchText.cpp b/Utils/_DutchText.cpp index b292a98f..844538d6 100644 --- a/Utils/_DutchText.cpp +++ b/Utils/_DutchText.cpp @@ -4243,9 +4243,10 @@ STR16 sRepairsDoneString[] = L"%s is klaar met reparatie van eigen items", L"%s is klaar met reparatie van ieders wapens en bepantering", L"%s is klaar met reparatie van ieders uitrusting", - L"%s is klaar met reparatie van ieders vervoerde items", - L"%s is klaar met reparatie van ieders vervoerde items", - L"%s is klaar met reparatie van ieders vervoerde items", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4474,6 +4475,11 @@ STR16 New113Message[] = L"You should check your goggles", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index e7cb4e14..3254ba75 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -4250,14 +4250,16 @@ STR16 gzCreditNameFunny[]= L"(did items and loading screens too!)", // Joey \"Joeker\" Whelan", }; +// HEADROCK: Adjusted strings for better feedback, and added new string for LBE repair. STR16 sRepairsDoneString[] = { L"%s finished repairing own items", L"%s finished repairing everyone's guns & armor", L"%s finished repairing everyone's equipped items", - L"%s finished repairing everyone's carried items", - L"%s finished repairing everyone's carried items", - L"%s finished repairing everyone's carried items", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4486,6 +4488,11 @@ STR16 New113Message[] = L"You should check your goggles", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_FrenchText.cpp b/Utils/_FrenchText.cpp index 9f33b34a..1750f510 100644 --- a/Utils/_FrenchText.cpp +++ b/Utils/_FrenchText.cpp @@ -4256,9 +4256,10 @@ STR16 sRepairsDoneString[] = L"%s a termin la rparation de ses objets", L"%s a termin la rparation des armes & protections", L"%s a termin la rparation des objets ports", - L"%s a termin la rparation des objets transports", - L"%s a termin la rparation des objets transports", - L"%s a termin la rparation des objets transports", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4487,6 +4488,11 @@ STR16 New113Message[] = L"Vous devriez vrifier votre accessoire de vision", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_GermanText.cpp b/Utils/_GermanText.cpp index 9245fc03..89fd15ff 100644 --- a/Utils/_GermanText.cpp +++ b/Utils/_GermanText.cpp @@ -4058,9 +4058,10 @@ STR16 sRepairsDoneString[] = L"%s hat seine eigenen Gegenstnde repariert", L"%s hat die Waffen und Rstungen aller Teammitglieder repariert", L"%s hat die aktivierten Gegenstnde aller Teammitglieder repariert", - L"%s hat die mitgefhrten Gegenstnde aller Teammitglieder repariert", - L"%s hat die mitgefhrten Gegenstnde aller Teammitglieder repariert", - L"%s hat die mitgefhrten Gegenstnde aller Teammitglieder repariert", + L"%s hat die groen mitgefhrten Gegenstnde aller Teammitglieder repariert", + L"%s hat die mittelgroen mitgefhrten Gegenstnde aller Teammitglieder repariert", + L"%s hat die kleinen mitgefhrten Gegenstnde aller Teammitglieder repariert", + L"%s hat die LBE-Gegenstnde aller Teammitglieder repariert", }; STR16 zGioDifConfirmText[]= @@ -4282,6 +4283,11 @@ STR16 New113Message[] = L"Sie sollten Ihre Brillen prfen", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_ItalianText.cpp b/Utils/_ItalianText.cpp index 333818b9..debed2a6 100644 --- a/Utils/_ItalianText.cpp +++ b/Utils/_ItalianText.cpp @@ -4235,9 +4235,10 @@ STR16 sRepairsDoneString[] = L"%s ha finito di riparare gli oggetti", L"%s ha finito di riparare le armi e i giubbotti antiproiettile di tutti", L"%s ha finito di riparare gli oggetti dell'equipaggiamento di tutti", - L"%s ha finito di riparare gli oggetti trasportati di tutti", - L"%s ha finito di riparare gli oggetti trasportati di tutti", - L"%s ha finito di riparare gli oggetti trasportati di tutti", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4472,6 +4473,11 @@ STR16 New113Message[] = L"You should check your goggles", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_PolishText.cpp b/Utils/_PolishText.cpp index 2e348a50..6a619f95 100644 --- a/Utils/_PolishText.cpp +++ b/Utils/_PolishText.cpp @@ -4248,9 +4248,10 @@ STR16 sRepairsDoneString[] = L"%s skoczy(a) naprawia wasne wyposaenie", L"%s skoczy(a) naprawia bro i ochraniacze wszystkich czonkw oddziau", L"%s skoczy(a) naprawia wyposaenie wszystkich czonkw oddziau", - L"%s skoczy(a) naprawia ekwipunek wszystkich czonkw oddziau", - L"%s zakoczy(a) napraw ekwipunku oddziau", - L"%s zakoczy(a) napraw ekwipunku oddziau", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4479,6 +4480,11 @@ STR16 New113Message[] = L"Sprawd swoje gogle", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_RussianText.cpp b/Utils/_RussianText.cpp index 3b13e8c4..7bb5add6 100644 --- a/Utils/_RussianText.cpp +++ b/Utils/_RussianText.cpp @@ -4250,9 +4250,10 @@ STR16 sRepairsDoneString[] = L"%s: .", L"%s: .", L"%s: .", - L"%s: , .", - L"%s: , .", - L"%s: , .", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4482,6 +4483,11 @@ STR16 New113Message[] = L" .", //You should check your goggles }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] = diff --git a/Utils/_TaiwaneseText.cpp b/Utils/_TaiwaneseText.cpp index d2b3221d..b2f90391 100644 --- a/Utils/_TaiwaneseText.cpp +++ b/Utils/_TaiwaneseText.cpp @@ -4255,9 +4255,10 @@ STR16 sRepairsDoneString[] = L"%s finished repairing own items", L"%s finished repairing everyone's guns & armor", L"%s finished repairing everyone's equipped items", - L"%s finished repairing everyone's carried items", - L"%s finished repairing everyone's carried items", - L"%s finished repairing everyone's carried items", + L"%s finished repairing everyone's large carried items", + L"%s finished repairing everyone's medium carried items", + L"%s finished repairing everyone's small carried items", + L"%s finished repairing everyone's LBE gear", }; STR16 zGioDifConfirmText[]= @@ -4486,6 +4487,11 @@ STR16 New113Message[] = L"You should check your goggles", }; +STR16 New113HAMMessage[] = +{ + L"%s cowers in fear!", +}; + // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends // INFO: Do not replace the characters. They indicate the (-> Newline) from the edt files STR16 New113MERCMercMailTexts[] =