From f010769397658b6a6be149dfab57c0ccf6cea838 Mon Sep 17 00:00:00 2001 From: Wanne Date: Sat, 20 Feb 2010 15:49:46 +0000 Subject: [PATCH] ************************* * HAM 3.6 (by Headrock) * ************************* - Info: Needed GameDir files for HAM 3.6 are not committed yet to the SVN GameDir. Will do that in the next few days - For more infos on HAM 3.6 check out: http://www.ja-galaxy-forum.com/board/ubbthreads.php?ubb=showflat&Number=244808&page=0&fpart=1 git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@3323 3b4a5df2-a311-0410-b5c6-a8a6f20db521 --- Editor/Editor_2005Express.vcproj | 2 +- GameSettings.cpp | 325 +- GameSettings.h | 272 +- GameVersion.cpp | 8 +- Init.cpp | 51 + Init.h | 13 + Laptop/AimFacialIndex.cpp | 11 +- Laptop/AimMembers.cpp | 10 +- Laptop/IMP Begin Screen.cpp | 4 +- Laptop/PostalService.h | 2 +- Laptop/files.cpp | 25 +- Laptop/finances.cpp | 39 +- Laptop/finances.h | 5 +- Laptop/mercs Files.cpp | Bin 60000 -> 60600 bytes Laptop/personnel.cpp | 88 +- SaveLoadGame.cpp | 55 +- Standard Gaming Platform/mousesystem.cpp | 42 +- Strategic/AI Viewer.cpp | 3 +- Strategic/Assignments.cpp | 3869 +++++++++++++++-- Strategic/Assignments.h | 59 +- Strategic/Auto Resolve.cpp | 7 +- Strategic/Campaign Init.cpp | 30 +- Strategic/Campaign Types.h | 218 +- Strategic/Facilities.cpp | 1733 ++++++++ Strategic/Facilities.h | 78 + Strategic/Game Init.cpp | 80 +- Strategic/Hourly Update.cpp | 10 + Strategic/Map Screen Helicopter.cpp | 13 +- Strategic/Map Screen Interface Border.cpp | 4 +- Strategic/Map Screen Interface Bottom.cpp | 121 +- Strategic/Map Screen Interface Bottom.h | 3 + .../Map Screen Interface Map Inventory.cpp | 4 +- Strategic/Map Screen Interface Map.cpp | 164 +- .../Map Screen Interface TownMine Info.cpp | 66 +- Strategic/Map Screen Interface.cpp | 99 +- Strategic/Map Screen Interface.h | 24 +- Strategic/MilitiaSquads.cpp | 1680 +++++-- Strategic/MilitiaSquads.h | 28 +- Strategic/Player Command.cpp | 93 +- Strategic/Player Command.h | 2 +- Strategic/Queen Command.cpp | 28 +- Strategic/Quests.cpp | 5 +- Strategic/Quests.h | 3 +- Strategic/Reinforcement.cpp | 31 +- Strategic/Strategic AI.cpp | 7 +- Strategic/Strategic Merc Handler.cpp | 15 +- Strategic/Strategic Mines.cpp | 45 +- Strategic/Strategic Movement.cpp | 122 +- Strategic/Strategic Town Loyalty.cpp | 25 +- Strategic/Strategic Town Loyalty.h | 3 + Strategic/Strategic_2005Express.vcproj | 32 + Strategic/Strategic_VS2008.vcproj | 40 +- Strategic/Town Militia.cpp | 998 ++++- Strategic/Town Militia.h | 33 +- Strategic/XML_Bloodcats.cpp | 343 ++ Strategic/XML_DynamicRestrictions.cpp | 223 + Strategic/XML_Facilities.cpp | 217 + Strategic/XML_FacilityTypes.cpp | 1117 +++++ Strategic/XML_Roaming.cpp | 1 - Strategic/XML_SectorNames.cpp | 232 + Strategic/XML_UniformColors.cpp | 260 ++ Strategic/mapscreen.cpp | 340 +- Strategic/mapscreen.h | 4 + Strategic/strategicmap.cpp | 285 +- Tactical/Boxing.cpp | 14 +- Tactical/Campaign.cpp | 151 +- Tactical/DisplayCover.cpp | 5 +- Tactical/Faces.cpp | 19 +- Tactical/Handle Items.cpp | 24 +- Tactical/Interface Enhanced.cpp | 24 +- Tactical/Interface Items.cpp | 45 +- Tactical/Interface Panels.cpp | 204 +- Tactical/Items.cpp | 285 +- Tactical/Items.h | 5 +- Tactical/LOS.cpp | 61 +- Tactical/Merc Entering.cpp | 40 +- Tactical/Merc Hiring.cpp | 29 +- Tactical/Morale.cpp | 41 +- Tactical/Overhead Types.h | 5 +- Tactical/Overhead.cpp | 477 +- Tactical/Overhead.h | 4 + Tactical/PATHAI.cpp | 26 +- Tactical/Points.cpp | 88 +- Tactical/Rotting Corpses.h | 3 +- Tactical/Soldier Control.cpp | 77 +- Tactical/Soldier Control.h | 29 +- Tactical/Soldier Create.cpp | 54 +- Tactical/Soldier Init List.cpp | 33 +- Tactical/Soldier Profile.cpp | 100 + Tactical/Soldier Profile.h | 86 + Tactical/Tactical Save.cpp | 26 +- Tactical/Tactical_2005Express.vcproj | 14 +- Tactical/Tactical_VS2008.vcproj | 12 + Tactical/TeamTurns.cpp | 24 +- Tactical/Turn Based Input.cpp | 420 +- Tactical/UI Cursors.cpp | 22 +- Tactical/Vehicles.cpp | 13 +- Tactical/Weapons.cpp | 337 +- Tactical/Weapons.h | 5 + Tactical/XML.h | 42 + Tactical/XML_Opinions.cpp | 899 ++++ Tactical/XML_Profiles.cpp | 1190 +++++ Tactical/XML_Profiles.h | 0 Tactical/XML_SpreadPatterns.cpp | 2 +- Tactical/bullets.cpp | 4 +- Tactical/opplist.cpp | 80 +- Tactical/opplist.h | 2 + TacticalAI/Attacks.cpp | 10 +- TacticalAI/DecideAction.cpp | 16 +- TacticalAI/Movement.cpp | 2 +- TileEngine/Explosion Control.cpp | 16 + Utils/Cinematics.cpp | 1 - Utils/Cursors.cpp | 11 +- Utils/STIConvert.cpp | 2 - Utils/Text.h | 6 + Utils/Utilities.cpp | 8 +- Utils/_EnglishText.cpp | 173 +- Utils/message.cpp | 98 +- ja2_2005Express.vcproj | 12 +- ja2_VS2008.vcproj | 12 +- 120 files changed, 16937 insertions(+), 2200 deletions(-) create mode 100644 Strategic/Facilities.cpp create mode 100644 Strategic/Facilities.h create mode 100644 Strategic/XML_Bloodcats.cpp create mode 100644 Strategic/XML_DynamicRestrictions.cpp create mode 100644 Strategic/XML_Facilities.cpp create mode 100644 Strategic/XML_FacilityTypes.cpp create mode 100644 Strategic/XML_SectorNames.cpp create mode 100644 Strategic/XML_UniformColors.cpp create mode 100644 Tactical/XML_Opinions.cpp create mode 100644 Tactical/XML_Profiles.cpp create mode 100644 Tactical/XML_Profiles.h diff --git a/Editor/Editor_2005Express.vcproj b/Editor/Editor_2005Express.vcproj index dcccc456..606b35a4 100644 --- a/Editor/Editor_2005Express.vcproj +++ b/Editor/Editor_2005Express.vcproj @@ -1,7 +1,7 @@ 0, then at least one of every new group will be an Elite. 100 = All Elites. + gGameExternalOptions.ubPercentRoamingMilitiaElites = iniReader.ReadInteger("JA2 HAM Settings","PERCENT_ROAMING_MILITIA_ELITES", 100, 0, 100); + + // HEADROCK HAM 3.4: What percentage of a new Mobile Militia group will be made of Regulars? If >0, then at least one of every new group will be a Regular. 100 = All Regulars. + gGameExternalOptions.ubPercentRoamingMilitiaRegulars = iniReader.ReadInteger("JA2 HAM Settings","PERCENT_ROAMING_MILITIA_REGULARS", 0, 0, 100); + + // HEADROCK HAM 3.5: Does leadership affect the Quality of new Mobile Militia groups? + gGameExternalOptions.fLeadershipAffectsMobileMilitiaQuality = iniReader.ReadBoolean("JA2 HAM Settings","LEADERSHIP_AFFECTS_MOBILE_MILITIA_QUALITY", FALSE); + + // HEADROCK HAM 3.5: Explosive Suppression Effectiveness alters the amount of Suppression Points you get from nearby blasts. + gGameExternalOptions.usExplosionSuppressionEffect = iniReader.ReadInteger("JA2 HAM Settings","EXPLOSIVE_SUPPRESSION_EFFECTIVENESS", 100, 0, 1000); + + // HEADROCK HAM 3.5: When enabled, goggle-switching affects every merc in the sector, not just those belonging to the current squad. + gGameExternalOptions.fGoggleSwapAffectsAllMercsInSector = iniReader.ReadBoolean("JA2 HAM Settings","GOGGLE_SWAP_AFFECTS_ALL_MERCS_IN_SECTOR", FALSE); + + // HEADROCK HAM 3.5: Helicopter - Base cost per tile in "GREEN" airspace + gGameExternalOptions.usHelicopterBaseCostPerGreenTile = iniReader.ReadInteger("JA2 HAM Settings","HELICOPTER_BASE_COST_PER_GREEN_TILE", 100, 0, 60000); + + // HEADROCK HAM 3.5: Helicopter - Base cost per tile in "RED" airspace + gGameExternalOptions.usHelicopterBaseCostPerRedTile = iniReader.ReadInteger("JA2 HAM Settings","HELICOPTER_BASE_COST_PER_RED_TILE", 1000, 0, 60000); + + // HEADROCK HAM 3.5: Experimental, arrival sectorX/Y. + gGameExternalOptions.ubDefaultArrivalSectorX = iniReader.ReadInteger("JA2 HAM Settings","DEFAULT_ARRIVAL_SECTOR_X", 9, 1, 16); + gGameExternalOptions.ubDefaultArrivalSectorY = iniReader.ReadInteger("JA2 HAM Settings","DEFAULT_ARRIVAL_SECTOR_Y", 1, 1, 16); + + // HEADROCK HAM 3.5: Limit bonus from tracers based on range to target. This is a multiplier factor - higher means harder to aim with tracers. + gGameExternalOptions.ubRangeDifficultyAimingWithTracers = iniReader.ReadInteger("JA2 HAM Settings","RANGE_EFFECT_ON_MAX_TRACER_CTH_BONUS", 3, 1, 10); + + // HEADROCK HAM 3.6: Militia can now place blue flags when they spot a landmine. + gGameExternalOptions.fMilitiaPlaceBlueFlags = iniReader.ReadBoolean("JA2 HAM Settings","MILITIA_PLACE_FLAGS_ON_MINES", FALSE); + + // HEADROCK PROFEX/3.6: Activate this to read Profile data from MercProfiles.XML and MercOpinions.XML + gGameExternalOptions.fReadProfileDataFromXML = iniReader.ReadBoolean("JA2 HAM Settings","READ_PROFILE_DATA_FROM_XML", FALSE); + + // HEADROCK PROFEX/3.6: Activate this to write Profile data to MercProfiles Out.XML and MercOpinions Out.XML. This can be used to convert PROF.DAT to XML format. + gGameExternalOptions.fWriteProfileDataToXML = iniReader.ReadBoolean("JA2 HAM Settings","WRITE_PROFILE_DATA_TO_XML", FALSE); + + // HEADROCK HAM 3.6: Progress bars for each stat, displayed behind the stat value on the merc panel. INI settings give RGB color, Menu setting toggles on/off. + gGameExternalOptions.ubStatProgressBarsRed = iniReader.ReadInteger("JA2 HAM Settings","STAT_PROGRESS_BARS_RED", 200, 0, 255); + gGameExternalOptions.ubStatProgressBarsGreen = iniReader.ReadInteger("JA2 HAM Settings","STAT_PROGRESS_BARS_GREEN", 0, 0, 255); + gGameExternalOptions.ubStatProgressBarsBlue = iniReader.ReadInteger("JA2 HAM Settings","STAT_PROGRESS_BARS_BLUE", 0, 0, 255); + + // HEADROCK HAM 3.6: Determines how likely a character is, every hour, to trigger a facility event. The actual chance is X in every Y attempts, where X is facility-specific, and Y is the value adjusted here. + gGameExternalOptions.usFacilityEventRarity = iniReader.ReadInteger("JA2 HAM Settings","FACILITY_EVENT_RARITY", 1000, 0, 50000); + // HEADROCK HAM 3.6: Controls how important the character's stats are, in insuring he/she gets better results (or smaller damage) from using facilities. The higher this is, the more dangerous all facilities are. + gGameExternalOptions.ubFacilityDangerRate = iniReader.ReadInteger("JA2 HAM Settings","FACILITY_DANGER_RATE", 50, 0, 100); + + // HEADROCK HAM 3.6: Determines whether the extrapolated daily costs of mercs are figured into the "Daily Expenses" display. 0 = No, just facility costs. 1 = Only mercs with a fixed daily rate. 2 = All mercs, including AIM contracts. + gGameExternalOptions.ubIncludeContractsInExpenses = iniReader.ReadInteger("JA2 HAM Settings","INCLUDE_CONTRACTS_IN_EXPENSES_DISPLAY", 1, 0, 2); + + // HEADROCK HAM 3.6: Maximum number of messages displayed in Tactical view + gGameExternalOptions.ubMaxMessagesTactical = iniReader.ReadInteger("JA2 HAM Settings","MAXIMUM_MESSAGES_IN_TACTICAL", 6, 1, 36); + switch (iResolution) + { + case 0: + gGameExternalOptions.ubMaxMessagesTactical = __max(20, gGameExternalOptions.ubMaxMessagesTactical); + break; + case 1: + gGameExternalOptions.ubMaxMessagesTactical = __max(26, gGameExternalOptions.ubMaxMessagesTactical); + break; + } + + // HEADROCK HAM 3.6: Daily upkeep costs for militia + gGameExternalOptions.usDailyCostTownGreen = iniReader.ReadInteger("JA2 HAM Settings","DAILY_MILITIA_UPKEEP_TOWN_GREEN", 0, 0, 10000); + gGameExternalOptions.usDailyCostTownRegular = iniReader.ReadInteger("JA2 HAM Settings","DAILY_MILITIA_UPKEEP_TOWN_REGULAR", 0, 0, 10000); + gGameExternalOptions.usDailyCostTownElite = iniReader.ReadInteger("JA2 HAM Settings","DAILY_MILITIA_UPKEEP_TOWN_ELITE", 0, 0, 10000); + gGameExternalOptions.usDailyCostMobileGreen = iniReader.ReadInteger("JA2 HAM Settings","DAILY_MILITIA_UPKEEP_MOBILE_GREEN", 0, 0, 10000); + gGameExternalOptions.usDailyCostMobileRegular = iniReader.ReadInteger("JA2 HAM Settings","DAILY_MILITIA_UPKEEP_MOBILE_REGULAR", 0, 0, 10000); + gGameExternalOptions.usDailyCostMobileElite = iniReader.ReadInteger("JA2 HAM Settings","DAILY_MILITIA_UPKEEP_MOBILE_ELITE", 0, 0, 10000); + + // HEADROCK HAM 3.6: Non-Combat Bodytypes shouldn't become hostile + gGameExternalOptions.fCanTrueCiviliansBecomeHostile = iniReader.ReadBoolean("JA2 HAM Settings","CAN_TRUE_CIVILIANS_BECOME_HOSTILE", TRUE); + + // HEADROCK HAM 3.6: Militia become hostile when attacked. 0 = No. 1 = If killed. 2 = If attacked (JA2 Default) + gGameExternalOptions.ubCanMilitiaBecomeHostile = iniReader.ReadInteger("JA2 HAM Settings","CAN_MILITIA_BECOME_HOSTILE", 2, 0, 2); + + // HEADROCK HAM 3.6: If activated, the game does not switch focus to a merc who spots an enemy in real-time mode. This fixes issues with Real-Time Sneak. + gGameExternalOptions.fNoAutoFocusChangeInRealtimeSneak = iniReader.ReadBoolean("JA2 HAM Settings","NO_AUTO_FOCUS_CHANGE_IN_REALTIME_SNEAK", FALSE); } INT16 DynamicAdjustAPConstants(INT16 iniReadValue, INT16 iniDefaultValue, BOOLEAN reverse) @@ -1259,6 +1429,15 @@ void LoadGameAPBPConstants() APBPConstants[AP_OPEN_ZIPPER] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_OPEN_ZIPPER",24),24); APBPConstants[AP_CLOSE_ZIPPER] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_CLOSE_ZIPPER",28),28); APBPConstants[AP_CLICK_AIM] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_CLICK_AIM",4),4); + // HEADROCK HAM 3.1: Separate costs for each aiming click (when using a scope): + APBPConstants[AP_FIRST_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_FIRST_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_SECOND_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_SECOND_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_THIRD_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_THIRD_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_FOURTH_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_FOURTH_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_FIFTH_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_FIFTH_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_SIXTH_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_SIXTH_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_SEVENTH_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_SEVENTH_CLICK_AIM_SCOPE",4),4); + APBPConstants[AP_EIGHTTH_CLICK_AIM_SCOPE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_EIGHTTH_CLICK_AIM_SCOPE",4),4); APBPConstants[AUTOFIRE_SHOTS_AP_VALUE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AUTOFIRE_SHOTS_AP_VALUE",20),20); APBPConstants[BAD_AP_COST] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","BAD_AP_COST",36),36); APBPConstants[AP_RELOAD_LOOSE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_RELOAD_LOOSE",8),8); @@ -1268,6 +1447,10 @@ void LoadGameAPBPConstants() APBPConstants[AP_MIN_LIMIT] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_MIN_LIMIT",-100),-100); APBPConstants[AP_LOST_PER_MORALE_DROP] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_LOST_PER_MORALE_DROP",12),12); APBPConstants[AP_SUPPRESSION_MOD] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_SUPPRESSION_MOD",24),24); + // HEADROCK HAM 3.5: Determines divisor for AP/Shock ratio. 100AP will have this set at 4, 25AP will have this set at 1. + APBPConstants[AP_SUPPRESSION_SHOCK_DIVISOR] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_SUPPRESSION_SHOCK_DIVISOR",4),4); + // HEADROCK HAM 3.2: Modifier for legshot AP loss based on damage + APBPConstants[AP_LOSS_PER_LEGSHOT_DAMAGE] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","AP_LOSS_PER_LEGSHOT_DAMAGE",4),4); APBPConstants[DEFAULT_APS] = DynamicAdjustAPConstants(iniReader.ReadInteger("APConstants","DEFAULT_APS",80),80); APBPConstants[DEFAULT_AIMSKILL] = iniReader.ReadInteger("APConstants","DEFAULT_AIMSKILL",80); diff --git a/GameSettings.h b/GameSettings.h index 7562757d..c85ac188 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -54,9 +54,14 @@ enum TOPTION_SILENT_SKYRIDER, TOPTION_LOW_CPU_USAGE, TOPTION_ENHANCED_DESC_BOX, - + // arynn - TOPTION_TOGGLE_TURN_MODE, + TOPTION_TOGGLE_TURN_MODE, + + // HEADROCK HAM 3.6: + TOPTION_STAT_PROGRESS_BARS, + + // arynn: Debug/Cheat TOPTION_CHEAT_MODE_OPTIONS_HEADER, TOPTION_FORCE_BOBBY_RAY_SHIPMENTS, // force all pending Bobby Ray shipments TOPTION_CHEAT_MODE_OPTIONS_END, @@ -80,7 +85,6 @@ enum TOPTION_HIDE_BULLETS, TOPTION_TRACKING_MODE, - NUM_ALL_GAME_OPTIONS, }; @@ -276,9 +280,10 @@ typedef struct UINT32 guiTrainVeteranMilitiaDelay; UINT32 guiCreateEachNHours; UINT32 guiDivOfOriginalMilitia; - UINT32 guiMinMilitiaSquadSize; - UINT32 guiMaxMilitiaSquadSize; - UINT32 guiMaxMilitiaSquadSizeBattle; + // HEADROCK HAM 3.6: These settings are REDUNDANT. + //UINT32 guiMinMilitiaSquadSize; + //UINT32 guiMaxMilitiaSquadSize; + //UINT32 guiMaxMilitiaSquadSizeBattle; //BOOLEAN gfAllowSoldierToolTips; // moved to prefereces - SANDRO @@ -419,9 +424,11 @@ typedef struct INT32 ubTrainingSkillMax; INT32 ubSelfTrainingDivisor; INT32 ubInstructedTrainingDivisor; - INT32 ubGunRangeTrainingBonus; + // HEADROCK HAM 3.5: No longer necessary. + //INT32 ubGunRangeTrainingBonus; INT32 ubTownMilitiaTrainingRate; - INT32 ubMaxMilitiaTrainersPerSector; + // HEADROCK HAM 3.5: No longer necessary. + //INT32 ubMaxMilitiaTrainersPerSector; INT32 ubTeachBonusToTrain; INT32 ubRpcBonusToTrainMilitia; INT32 ubMinSkillToTeach; @@ -502,7 +509,7 @@ BOOLEAN gbBulletTracer; // HEADROCK: Enhanced Item Description Box ON/OFF // WANNE: Changed from BOOLEAN to INT32! - INT32 fEnhancedDescriptionBox; + INT32 iEnhancedDescriptionBox; // WANNE: Always use "prof.dat". BOOLEAN fAlwaysUseProfDat; @@ -518,58 +525,46 @@ BOOLEAN gbBulletTracer; UINT8 ubCoverDisplayUpdateWait; // HEADROCK HAM B1: Global modifier for mine income (0 is automatically turned to 1 to avoid divide-by-zero.) - INT16 iMineIncomePercentage; + INT16 usMineIncomePercentage; // HEADROCK HAM B1: Allows changing the maximum CTH possible, for any attack 0-100 - UINT8 iMaximumCTH; + UINT8 ubMaximumCTH; // HEADROCK HAM B1: Allows changing the minimum CTH possible, for any attack 0-100 - UINT8 iMinimumCTH; + UINT8 ubMinimumCTH; // HEADROCK HAM B1: Allows reducing the chance of scoring a hit fractionally, if CTH = Minimum CTH - UINT8 iMinimumCTHDivisor; + UINT16 usMinimumCTHDivisor; // HEADROCK HAM B1: Allows Restricted Roaming Militia to move through previously visited sectors, even if restricted. - BOOLEAN bUnrestrictVisited; + BOOLEAN fUnrestrictVisited; // 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; + BOOLEAN fDynamicRestrictRoaming; // HEADROCK HAM B2.1: This controls how effective suppression is, by increasing the number of ubSuppressionPoints accumulated by combatants (percentage); - INT16 iSuppressionEffectiveness; + INT16 sSuppressionEffectiveness; - // HEADROCK HAM B2: MAXIMUM number of APs that can be lost to suppression in a given turn (0=unlimited) - BOOLEAN fSuppressionAPLossPerTurn; - - // HEADROCK HAM B2: MAXIMUM number of APs that can be lost to suppression in a given attack (0=unlimited) - BOOLEAN fSuppressionAPLossPerAttack; - - UINT8 iSuppressionToleranceMax; - UINT8 iSuppressionToleranceMin; - - // HEADROCK HAM B2: Suppression Shock ON/OFF - BOOLEAN fSuppressionShock; + UINT8 ubSuppressionToleranceMax; + UINT8 ubSuppressionToleranceMin; // HEADROCK HAM B2: Suppression Shock effectiveness (percentage, 100 = "normal", 0 = deactivated. Range 0-65535) - UINT16 iSuppressionShockEffectiveness; + UINT16 usSuppressionShockEffect; // HEADROCK HAM B2.1: CTH penalty given by a "Cowering" target to any enemy shooter. - INT8 iAimPenaltyPerTargetShock; + UINT8 ubAimPenaltyPerTargetShock; // HEADROCK HAM B2.3: It is now possible for "cowering" to increase a target's suppression. - UINT16 iCowerEffectOnSuppression; + UINT16 usCowerEffectOnSuppression; // 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; + UINT8 ubRealisticTracers; // 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; + UINT8 ubNumBulletsPerTracer; // HEADROCK HAM B2.5: Realistic tracers - CTH increased by this amount whenever a tracer is fired. 0 = off. - INT16 iCTHBumpPerTracer; + UINT8 ubCTHBumpPerTracer; // HEADROCK HAM B2.6: Increased aiming costs? BOOLEAN fIncreasedAimingCost; @@ -581,32 +576,32 @@ BOOLEAN gbBulletTracer; FLOAT iMovementEffectOnAiming; // HEADROCK HAM B2.6: Autofire Bullets/5AP modifier - INT16 iAutofireBulletsPer5APModifier; + INT8 bAutofireBulletsPer5APModifier; // 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; + UINT8 ubMaxSuppressionShock; // HEADROCK HAM B2.6/2/1: Toggle new Burst/Auto CTH bars: 0=neither, 1=both, 2=Burst, 3=Auto - UINT8 iNewCTHBars; + UINT8 ubNewCTHBars; // 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; + UINT16 usHealthSubpointsToImprove; + UINT16 usStrengthSubpointsToImprove; + UINT16 usDexteritySubpointsToImprove; + UINT16 usAgilitySubpointsToImprove; + UINT16 usWisdomSubpointsToImprove; + UINT16 usMarksmanshipSubpointsToImprove; + UINT16 usMedicalSubpointsToImprove; + UINT16 usMechanicalSubpointsToImprove; + UINT16 usExplosivesSubpointsToImprove; + UINT16 usLeadershipSubpointsToImprove; + UINT16 usLevelSubpointsToImprove; // 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; @@ -614,15 +609,6 @@ BOOLEAN gbBulletTracer; // 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; @@ -648,9 +634,175 @@ BOOLEAN gbBulletTracer; // 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; - // HEADROCK HAM 3: Show Tooltips from Attachments in Bobby Rays shop= + // HEADROCK HAM 3: Externalized ratio between Weight and Strength. This value determines how many strength points we need to lift 0.5kg with no encumberance. + FLOAT iStrengthToLiftHalfKilo; + + // HEADROCK HAM 3: Define minimum leadership required for training militia. Set to 0 for "no limit". + UINT8 ubMinimumLeadershipToTrainMilitia; + + // HEADROCK HAM 3: Define effect of "TEACHER" trait in increasing effective leadership, for purposes of eligibility for training militia + UINT16 usTeacherTraitEffectOnLeadership; + + // HEADROCK HAM 3: If enabled, the trainer's "effective" leadership skill determines HOW MANY militia he/she creates per session (only works in town militia training). + BOOLEAN fLeadershipAffectsMilitiaQuantity; + + // HEADROCK HAM 3: If "LEADERSHIP_AFFECTS_MILITIA_QUANTITY" is true, this value determines the lowest leadership required to train a full (default size 10) squad of town militia in one training session. + UINT8 ubReqLeadershipForFullTraining; + + // HEADROCK HAM 3: Four INI settings to control the number of kills you must accumulate to win one progress point, one setting per difficulty level + UINT16 usNumKillsPerProgressPointNovice; + UINT16 usNumKillsPerProgressPointExperienced; + UINT16 usNumKillsPerProgressPointExpert; + UINT16 usNumKillsPerProgressPointInsane; + + // HEADROCK HAM 3: If enabled, changes the way the game calculates progress. It will look at each progress control separately (Kills/Income/Control/Visited) and set the progress to the HIGHEST scoring control. + BOOLEAN fAlternateProgressCalculation; + + // HEADROCK HAM 3: If enabled, tooltipping over Bobby Ray's weapons will show a list of possible attachments to those weapons. BOOLEAN fBobbyRayTooltipsShowAttachments; + // HEADROCK HAM 3.1: Divisor for the AP-to-Ready cost charge on first aiming click, when extra aiming costs are enabled. 0 = No ready-time-based charge. + UINT8 ubFirstAimReadyCostDivisor; + + // HEADROCK HAM 3.1: This is a suppression tool that not everyone will like. It gives an on-screen message when any character has been suppressed so much he's lost his next turn completely! + BOOLEAN fShowSuppressionShutdown; + + // HEADROCK HAM 3.1: Set whether you'd like to determine yourself which mine (if any) will shut down during the campaign. + BOOLEAN fManuallySelectMineShutdown; + + // HEADROCK HAM 3.1: Select which mine will run out. 0 = no mine. 1 = San Mona (unused), 2 = Drassen, 3 = Alma, 4 = Cambria, 5 = Chitzena, 6 = Grumm. + UINT8 ubWhichMineRunsOut; + + // HEADROCK HAM 3.1: Can the Humvee go off-road? + BOOLEAN fHumveeOffroad; + + // HEADROCK HAM 3.2: If activated, reinforcements (militia/enemy) arrive in the battle with 0 APs. This makes them less of a diablo-ex-machina. + UINT8 ubReinforcementsFirstTurnFreeze; + + // HEADROCK HAM 3.2: This feature allows the status, leadership and experience of nearby friendlies help/hinder a character's tolerance, based on their distance from him. + BOOLEAN fFriendliesAffectTolerance; + + // HEADROCK HAM 3.2: Set a divisor for the CtH of Mortar weapons. + UINT8 ubMortarCTHDivisor; + + // HEADROCK HAM 3.2: This enabled reduced sight for cowering characters. 0 = disabled. 2 = Reduced Sightrange. 3 = Tunnel-vision. 1 = Both. + UINT8 ubCoweringReducesSightRange; + + // HEADROCK HAM 3.2: Critical Headshots may cause blindness. Rolls 1 to X change of being blinded. 0 = disabled. + UINT8 ubChanceBlindedByHeadshot; + + // HEADROCK HAM 3.2: Critical Legshots cause additional AP loss + BOOLEAN fCriticalLegshotCausesAPLoss; + + // HEADROCK HAM 3.2: When enabled, this setting removes the player's omniscience inside his own sectors. The player will no longer see the movement of enemy groups unless someone is nearby to scout them. + BOOLEAN fNoEnemyDetectionWithoutRecon; + + // HEADROCK HAM 3.2: Determines the training bonus for the Medical skill, when training in "hospital" sectors + UINT8 ubHospitalTrainingBonus; + + // HEADROCK HAM 3.2: Determines the repaid bonus when training in factory ("industrial") sectors + UINT8 ubFactoryRepairBonus; + + // HEADROCK HAM 3.2: How much MECHANICAL skill do we need to gain a repair bonus from a factory? + UINT8 ubMinMechanicalForFactoryRepairBonus; + + // HEADROCK HAM 3.3: Externalized maximum possible penalty for hitting a moving target. JA2 Default = 30. + UINT16 usMaxCTHPenaltyForMovingTarget; + + // HEADROCK HAM 3.3: Increases tolerance while moving. + UINT8 ubTilesMovedPerBonusTolerancePoint; + + // HEADROCK HAM 3.3: New militia feature, Minimum Leadership required to train Roaming Militia + UINT8 ubMinimumLeadershipToTrainMobileMilitia; + + // HEADROCK HAM 3.3: If enabled, the trainer's "effective" leadership skill determines HOW MANY militia he/she creates per session. + BOOLEAN fLeadershipAffectsMobileMilitiaQuantity; + + // HEADROCK HAM 3.3: If "LEADERSHIP_AFFECTS_MILITIA_QUANTITY" is true, this value determines the lowest leadership required to train a full (default size 10) squad of town militia in one training session. + UINT8 ubReqLeadershipForFullMobileTraining; + + // HEADROCK HAM 3.3: Minimum distance (in METERS) at which character suffer from friendly suppression. + UINT16 usMinDistanceFriendlySuppression; + + // HEADROCK HAM 3.3: If enabled, Roaming Militia can automatically reinforce city garrisons. + BOOLEAN fAllowMobileReinforceCities; + + // HEADROCK HAM 3.3: If enabled, Roaming Militia can automatically reinforce city garrisons. + BOOLEAN fAllowMobileReinforceSAM; + + // HEADROCK HAM 3.4: This controls the intensity of Hiding the Bullet Count during combat. The higher it is, the more intense the effect. Negative values reduce the effect. + UINT16 usBulletHideIntensity; + + // HEADROCK HAM 3.4: What percentage of a new Mobile Militia group will be made of Elites? If >0, then at least one of every group will be an Elite. 100 = All Elites. + UINT8 ubPercentRoamingMilitiaElites; + + // HEADROCK HAM 3.4: What percentage of a new Mobile Militia group will be made of Elites? If >0, then at least one of every group will be an Elite. 100 = All Elites. + UINT8 ubPercentRoamingMilitiaRegulars; + + // HEADROCK HAM 3.5: Does leadership affect the Quality of new Mobile Militia groups? + BOOLEAN fLeadershipAffectsMobileMilitiaQuality; + + // HEADROCK HAM 3.5: Explosive Suppression Effectiveness alters the amount of Suppression Points you get from nearby blasts. + UINT16 usExplosionSuppressionEffect; + + // HEADROCK HAM 3.5: When enabled, goggle-switching affects every merc in the sector, not just those belonging to the current squad. + BOOLEAN fGoggleSwapAffectsAllMercsInSector; + + // HEADROCK HAM 3.5: Helicopter - Base cost per tile in "GREEN" airspace + UINT16 usHelicopterBaseCostPerGreenTile; + + // HEADROCK HAM 3.5: Helicopter - Base cost per tile in "RED" airspace + UINT16 usHelicopterBaseCostPerRedTile; + + // HEADROCK HAM 3.5: Experimental, arrival sectorX/Y. + UINT8 ubDefaultArrivalSectorX; + UINT8 ubDefaultArrivalSectorY; + + // HEADROCK HAM 3.5: Limit bonus from tracers based on range to target. This is a divisor for the effect. + UINT8 ubRangeDifficultyAimingWithTracers; + + // HEADROCK HAM 3.6: Militia can now place flags on discovered mines. + BOOLEAN fMilitiaPlaceBlueFlags; + + // HEADROCK PROFEX: Activate this to read Profile data from Profiles.XML and Opinions.XML + BOOLEAN fReadProfileDataFromXML; + + // HEADROCK PROFEX: Activate this to read Profile data from Profiles.XML and Opinions.XML + BOOLEAN fWriteProfileDataToXML; + + // HEADROCK HAM 3.6: Progress bars for each stat, displayed behind the stat value on the merc panel. + UINT8 ubStatProgressBarsRed; + UINT8 ubStatProgressBarsGreen; + UINT8 ubStatProgressBarsBlue; + + // HEADROCK HAM 3.6: Determines how likely a character is, every hour, to trigger a facility event. The actual chance is X in every Y attempts, where X is facility-specific, and Y is the value adjusted here. + UINT16 usFacilityEventRarity; + // HEADROCK HAM 3.6: Controls how important the character's stats are, in insuring he/she gets better results (or smaller damage) from using facilities. The higher this is, the more dangerous all facilities are. + UINT8 ubFacilityDangerRate; + + // HEADROCK HAM 3.6: Determines whether the extrapolated daily costs of mercs are figured into the "Daily Expenses" display. 0 = No, just facility costs. 1 = Only mercs with a fixed daily rate. 2 = All mercs, including AIM contracts. + UINT8 ubIncludeContractsInExpenses; + + // HEADROCK HAM 3.6: Maximum number of messages displayed in Tactical view + UINT8 ubMaxMessagesTactical; + + // HEADROCK HAM 3.6: Daily upkeep costs for militia + UINT16 usDailyCostTownGreen; + UINT16 usDailyCostTownRegular; + UINT16 usDailyCostTownElite; + UINT16 usDailyCostMobileGreen; + UINT16 usDailyCostMobileRegular; + UINT16 usDailyCostMobileElite; + + // HEADROCK HAM 3.6: Non-Combat Bodytypes can't become hostile + BOOLEAN fCanTrueCiviliansBecomeHostile; + + // HEADROCK HAM 3.6: Militia become hostile when attacked. 0 = No. 1 = If killed. 2 = If attacked (JA2 Default) + UINT8 ubCanMilitiaBecomeHostile; + + // HEADROCK HAM 3.6: If activated, the game does not switch focus to a merc who spots an enemy in real-time mode. This fixes issues with Real-Time Sneak. + BOOLEAN fNoAutoFocusChangeInRealtimeSneak; + // The_Bob - real time sneaking code 01/06/09 // Suport disabling/silencing real time sneaking via external .ini file BOOLEAN fAllowRealTimeSneak; diff --git a/GameVersion.cpp b/GameVersion.cpp index 5d2e8821..af89bb14 100644 --- a/GameVersion.cpp +++ b/GameVersion.cpp @@ -13,12 +13,12 @@ #ifdef JA2EDITOR //MAP EDITOR BUILD VERSION -CHAR16 zVersionLabel[256] = { L"Map Editor v1.13.3023" }; +CHAR16 zVersionLabel[256] = { L"Map Editor v1.13.3322" }; #elif defined JA2BETAVERSION //BETA/TEST BUILD VERSION -CHAR16 zVersionLabel[256] = { L"Debug v1.13.3287" }; +CHAR16 zVersionLabel[256] = { L"Debug v1.13.3322" }; #elif defined CRIPPLED_VERSION @@ -28,11 +28,11 @@ CHAR16 zVersionLabel[256] = { L"Beta v. 0.98" }; #else //RELEASE BUILD VERSION - CHAR16 zVersionLabel[256] = { L"Release v1.13.3287" }; + CHAR16 zVersionLabel[256] = { L"Release v1.13.3322" }; #endif -CHAR8 czVersionNumber[16] = { "Build 09.10.23" }; //YY.MM.DD +CHAR8 czVersionNumber[16] = { "Build 10.02.20" }; //YY.MM.DD CHAR16 zTrackingNumber[16] = { L"Z" }; diff --git a/Init.cpp b/Init.cpp index f5f22fae..d2c4dfb1 100644 --- a/Init.cpp +++ b/Init.cpp @@ -524,6 +524,57 @@ BOOLEAN LoadExternalGameplayData(STR directoryName) DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); THROWIFFALSE(ReadInDeliveryMethods(fileName),DELIVERYMETHODSFILENAME); + // HEADROCK HAM 3.5: Read in facility types + strcpy(fileName, directoryName); + strcat(fileName, FACILITYTYPESFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInFacilityTypes(fileName), FACILITYTYPESFILENAME); + + // HEADROCK HAM 3.4: Read in facility locations + strcpy(fileName, directoryName); + strcat(fileName, SECTORFACILITIESFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInSectorFacilities(fileName), SECTORFACILITIESFILENAME); + + // HEADROCK HAM 3.4: Read in dynamic roaming restrictions + strcpy(fileName, directoryName); + strcat(fileName, DYNAMICROAMINGFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInDynamicRoamingRestrictions(fileName), DYNAMICROAMINGFILENAME); + + // HEADROCK HAM 3.6: Read in customized sector names + strcpy(fileName, directoryName); + strcat(fileName, SECTORNAMESFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInSectorNames(fileName), SECTORNAMESFILENAME); + + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Read in Merc Profile data to replace PROF.DAT data + strcpy(fileName, directoryName); + strcat(fileName, MERCPROFILESFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInMercProfiles(fileName), MERCPROFILESFILENAME); + + // HEADROCK PROFEX: Read in Merc Opinion data to replace PROF.DAT data + strcpy(fileName, directoryName); + strcat(fileName, MERCOPINIONSFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInMercOpinions(fileName), MERCOPINIONSFILENAME); + } + + // HEADROCK HAM 3.6: Read in customized Bloodcat Placements + strcpy(fileName, directoryName); + strcat(fileName, BLOODCATPLACEMENTSFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInBloodcatPlacements(fileName), BLOODCATPLACEMENTSFILENAME); + + // HEADROCK HAM 3.6: Read in customized Uniform Colors + strcpy(fileName, directoryName); + strcat(fileName, UNIFORMCOLORSFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + THROWIFFALSE(ReadInUniforms(fileName), UNIFORMCOLORSFILENAME); + return TRUE; } diff --git a/Init.h b/Init.h index cc643525..8d04954a 100644 --- a/Init.h +++ b/Init.h @@ -86,6 +86,15 @@ AP_BACK_PACK, AP_OPEN_ZIPPER, AP_CLOSE_ZIPPER, AP_CLICK_AIM, +// HEADROCK HAM 3.1: Eight different settings for each aiming click with a scope. +AP_FIRST_CLICK_AIM_SCOPE, +AP_SECOND_CLICK_AIM_SCOPE, +AP_THIRD_CLICK_AIM_SCOPE, +AP_FOURTH_CLICK_AIM_SCOPE, +AP_FIFTH_CLICK_AIM_SCOPE, +AP_SIXTH_CLICK_AIM_SCOPE, +AP_SEVENTH_CLICK_AIM_SCOPE, +AP_EIGHTTH_CLICK_AIM_SCOPE, AP_RELOAD_LOOSE, AP_UNJAM, AP_WRONG_MAG, @@ -94,6 +103,10 @@ AP_MAX_TURN_SUPPRESSED, AP_MIN_LIMIT, AP_LOST_PER_MORALE_DROP, AP_SUPPRESSION_MOD, +// HEADROCK HAM 3.5: Ratio of AP loss to Suppression Shock gain. +AP_SUPPRESSION_SHOCK_DIVISOR, +// HEADROCK HAM 3.3: AP loss per point of damage, inflicted when a character is knocked down by a hit to the legs. +AP_LOSS_PER_LEGSHOT_DAMAGE, AUTOFIRE_SHOTS_AP_VALUE, BAD_AP_COST, DEFAULT_APS, diff --git a/Laptop/AimFacialIndex.cpp b/Laptop/AimFacialIndex.cpp index dfc3f43c..1874c481 100644 --- a/Laptop/AimFacialIndex.cpp +++ b/Laptop/AimFacialIndex.cpp @@ -13,6 +13,7 @@ #include "Text.h" #include "aimsort.h" #include "Assignments.h" + #include "GameSettings.h" #endif @@ -98,7 +99,15 @@ BOOLEAN EnterAimFacialIndex() MSYS_AddRegion( &gMercFaceMouseRegions[ i ] ); MSYS_SetRegionUserData( &gMercFaceMouseRegions[ i ], 0, i); - sprintf(sTemp, "%s%02d.sti", sFaceLoc, AimMercArray[i]); + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%02d.sti", sFaceLoc, gMercProfiles[AimMercArray[i]].ubFaceIndex); + } + else + { + sprintf(sTemp, "%s%02d.sti", sFaceLoc, AimMercArray[i]); + } VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP(sTemp, VObjectDesc.ImageFile); if( !AddVideoObject(&VObjectDesc, &guiAimFiFace[i]) ) diff --git a/Laptop/AimMembers.cpp b/Laptop/AimMembers.cpp index 604c5145..09d57faf 100644 --- a/Laptop/AimMembers.cpp +++ b/Laptop/AimMembers.cpp @@ -1432,7 +1432,15 @@ BOOLEAN DisplayMercsFace() BltVideoObject(FRAME_BUFFER, hPortraitHandle, 0,PORTRAIT_X, PORTRAIT_Y, VO_BLT_SRCTRANSPARENCY,NULL); // load the Face graphic and add it - sprintf(sTemp, "%s%02d.sti", sFaceLoc, gbCurrentSoldier); + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%02d.sti", sFaceLoc, gMercProfiles[gbCurrentSoldier].ubFaceIndex); + } + else + { + sprintf(sTemp, "%s%02d.sti", sFaceLoc, gbCurrentSoldier); + } VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP(sTemp, VObjectDesc.ImageFile); CHECKF(AddVideoObject(&VObjectDesc, &guiFace)); diff --git a/Laptop/IMP Begin Screen.cpp b/Laptop/IMP Begin Screen.cpp index 542ae352..b71aaa55 100644 --- a/Laptop/IMP Begin Screen.cpp +++ b/Laptop/IMP Begin Screen.cpp @@ -604,7 +604,9 @@ void HandleBeginScreenTextEvent( UINT32 uiKey ) uiKey >= 'a' && uiKey <= 'z' || uiKey >= '0' && uiKey <= '9' || uiKey == '_' || uiKey == '.' || - uiKey == ' ' ) + uiKey == ' ' || uiKey == '"' || + uiKey == 39 // This is ' which cannot be written explicitly here of course + ) #else if( charSet::IsFromSet( uiKey, charSet::CS_SPACE|charSet::CS_ALPHA_NUM|charSet::CS_SPECIAL_ALPHA ) ) #endif diff --git a/Laptop/PostalService.h b/Laptop/PostalService.h index b567643f..5e09dc28 100644 --- a/Laptop/PostalService.h +++ b/Laptop/PostalService.h @@ -222,7 +222,7 @@ public: UINT16 RemoveDestination(UINT16 usDestinationID); RefToDestinationStruct GetDestination(UINT16 usDestinationID) const; RefToDestinationList LookupDestinationList(void) const; - + // WANNE: BOOLEAN IsSectorAShipmentSector(UINT8 ubMapX, UINT8 ubMapY, UINT8 ubMapZ); diff --git a/Laptop/files.cpp b/Laptop/files.cpp index e7cd645d..9776b91e 100644 --- a/Laptop/files.cpp +++ b/Laptop/files.cpp @@ -15,6 +15,9 @@ #include "cursors.h" #include "email.h" #include "text.h" + // HEADROCK PROFEX: This is required to display the proper facial image. + #include "Soldier Profile.h" + #include "GameSettings.h" #endif #define TOP_X LAPTOP_SCREEN_UL_X @@ -1759,16 +1762,30 @@ BOOLEAN HandleSpecialTerroristFile( INT32 iFileNumber, STR sPictureName ) // show picture if( ( giFilesPage == 0 ) && ( iCounter == 5 ) ) { - if( usProfileIdsForTerroristFiles[ iFileNumber + 1 ] < 100 ) + if (gGameExternalOptions.fReadProfileDataFromXML) { - sprintf(sTemp, "%s%02d.sti", "FACES\\BIGFACES\\", usProfileIdsForTerroristFiles[ iFileNumber + 1 ]); + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + if( usProfileIdsForTerroristFiles[ iFileNumber + 1 ] < 100 ) + { + sprintf(sTemp, "%s%02d.sti", "FACES\\BIGFACES\\", gMercProfiles[ usProfileIdsForTerroristFiles[ iFileNumber + 1 ] ].ubFaceIndex); + } + else + { + sprintf(sTemp, "%s%03d.sti", "FACES\\BIGFACES\\", gMercProfiles[ usProfileIdsForTerroristFiles[ iFileNumber + 1 ] ].ubFaceIndex); + } } else { - sprintf(sTemp, "%s%03d.sti", "FACES\\BIGFACES\\", usProfileIdsForTerroristFiles[ iFileNumber + 1 ]); + if( usProfileIdsForTerroristFiles[ iFileNumber + 1 ] < 100 ) + { + sprintf(sTemp, "%s%02d.sti", "FACES\\BIGFACES\\", usProfileIdsForTerroristFiles[ iFileNumber + 1 ]); + } + else + { + sprintf(sTemp, "%s%03d.sti", "FACES\\BIGFACES\\", usProfileIdsForTerroristFiles[ iFileNumber + 1 ]); + } } - VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP(sTemp, VObjectDesc.ImageFile); CHECKF(AddVideoObject(&VObjectDesc, &uiPicture)); diff --git a/Laptop/finances.cpp b/Laptop/finances.cpp index 32f2b823..8aded9e3 100644 --- a/Laptop/finances.cpp +++ b/Laptop/finances.cpp @@ -17,6 +17,10 @@ #include "LaptopSave.h" #include "Campaign Types.h" #include "strategicmap.h" + // HEADROCK HAM 3.6: Added facilities for calculating hourly expenses + #include "Facilities.h" + // HEADROCK HAM 3.6: Militia upkeep + #include "Town Militia.h" #endif // the global defines @@ -402,7 +406,27 @@ INT32 GetProjectedTotalDailyIncome( void ) // then there is how many deposits have been made, now look at how many mines we have, thier rate, amount of ore left and predict if we still // had these mines how much more would we get? - return( PredictIncomeFromPlayerMines() ); + // HEADROCK HAM 3.6: Facilities can make you money, and this is figured into your daily income. + + INT32 iRate = 0; + iRate = PredictIncomeFromPlayerMines() + (15 * GetTotalFacilityHourlyCosts( TRUE )); // 15 hours is the average time a merc can work per day + + return( iRate ); +} + +// HEADROCK HAM 3.6: Predict expenses today. Takes into account facilities, and in the future merc contracts. +INT32 GetProjectedExpenses( void ) +{ + INT32 iTotalExpenses = 0; + + INT32 iFacilityExpenses = GetTotalFacilityHourlyCosts( FALSE ); + INT32 iContractExpenses = GetTotalContractExpenses(); + + iTotalExpenses += (iFacilityExpenses*15); // 15 hours is the average time a merc can work every day. + iTotalExpenses += iContractExpenses; + iTotalExpenses += guiTotalUpkeepForMilitia; + + return (iTotalExpenses); } INT32 GetProjectedBalance( void ) @@ -1224,7 +1248,8 @@ UINT32 ProcessAndEnterAFinacialRecord( UINT8 ubCode, UINT32 uiDate, INT32 iAmoun else { // alloc space - uiId = ReadInLastElementOfFinanceListAndReturnIdNumber( ); + // HEADROCK HAM 3.6: Fix by Warmsteel to prevent repetitive entries on finance list. Next line commented out. + // uiId = ReadInLastElementOfFinanceListAndReturnIdNumber( ); pFinance = (FinanceUnitPtr) MemAlloc(sizeof(FinanceUnit)); // setup info passed @@ -1534,6 +1559,16 @@ void ProcessTransactionString(STR16 pString, FinanceUnitPtr pFinance) swprintf(pString, pTransactionText[ MERC_DEPOSITED_MONEY_TO_PLAYER_ACCOUNT ], gMercProfiles[ pFinance->ubSecondCode ].zNickname ); break; + // HEADROCK HAM 3.6: Paid for Facility Use + case( FACILITY_OPERATIONS ): + swprintf(pString, L"%s", pTransactionText[ FACILITY_OPERATIONS ]); + break; + + // HEADROCK HAM 3.6: Paid for militia upkeep + case( MILITIA_UPKEEP ): + swprintf(pString, L"%s", pTransactionText[ MILITIA_UPKEEP ]); + break; + } diff --git a/Laptop/finances.h b/Laptop/finances.h index b6559a57..f3185a0c 100644 --- a/Laptop/finances.h +++ b/Laptop/finances.h @@ -50,7 +50,9 @@ enum TRAIN_TOWN_MILITIA, PURCHASED_ITEM_FROM_DEALER, MERC_DEPOSITED_MONEY_TO_PLAYER_ACCOUNT, - SOLD_ITEMS + SOLD_ITEMS, + FACILITY_OPERATIONS, // HEADROCK HAM 3.6: Facility costs (daily payment) + MILITIA_UPKEEP, // HEADROCK HAM 3.6: Militia Upkeep Costs (daily payment) }; @@ -64,6 +66,7 @@ void InsertDollarSignInToString( STR16 pString ); void InsertCommasForDollarFigure( STR16 pString ); INT32 GetTodaysDaysIncome( void ); INT32 GetProjectedTotalDailyIncome( void ); +INT32 GetProjectedExpenses( void ); // HEADROCK HAM 3.6: Projected daily expenses. #endif diff --git a/Laptop/mercs Files.cpp b/Laptop/mercs Files.cpp index 01ae486d0802541087bf2b139f25993a46511584..3e70115ab842a2a7fce00498d183f2620468a8d4 100644 GIT binary patch delta 449 zcmZvYu}eZx6vn?WqXb(7r-;{-gXkJ95t$VwnKcLn^0KEs(w^uuT*ASIhPL}pGzJd! zFSIp8;8cG@e?dR55EOB^-1~j!JKs6?e7;4V$C1bPs9By#n)}Fn{8*ks!q=7RqnBvm zrsAt6M=8~{BUdd1+v+3n)IsE`sYA7GNdWFDgT6%8)rE>^0;mcYF=WuiA65!+DGVS8f$+xI#`S!1_6Mc`l&+Wu% z>L#D-qMTO43>YsUE`O3fmL4cn!`^=_Yp6TqT}}_{Jg+LsJ*+2{efVp*CRvY_#1+#5 z(PdQ~J3z=ubProfile )&&( 57 > MercPtrs[iId]->ubProfile ) ) { + if( ( 50 < MercPtrs[iId]->ubProfile )&&( 57 > MercPtrs[iId]->ubProfile ) ) + { sprintf( sTemp, "%s%03d.sti", FACES_DIR, gMercProfiles[ MercPtrs[iId]->ubProfile ].ubFaceIndex ); - } else { - sprintf(sTemp, "%s%02d.sti", FACES_DIR, Menptr[ iId ].ubProfile); + } + else + { + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%02d.sti", FACES_DIR, gMercProfiles[Menptr[ iId ].ubProfile].ubFaceIndex); + } + else + { + sprintf(sTemp, "%s%02d.sti", FACES_DIR, Menptr[ iId ].ubProfile); + } } if( MercPtrs[iId]->flags.uiStatusFlags & SOLDIER_VEHICLE ) { @@ -839,10 +850,21 @@ void RenderPersonnelFace(INT32 iId, INT32 iSlot, BOOLEAN fDead, BOOLEAN fFired, return; } - if( ( 50 < iId )&&( 57 > iId ) ) { + if( ( 50 < iId )&&( 57 > iId ) ) + { sprintf( sTemp, "%s%03d.sti", FACES_DIR, gMercProfiles[ iId ].ubFaceIndex ); - } else { - sprintf(sTemp, "%s%02d.sti", FACES_DIR, iId ); + } + else + { + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%02d.sti", FACES_DIR, gMercProfiles[Menptr[ iId ].ubProfile].ubFaceIndex ); + } + else + { + sprintf(sTemp, "%s%02d.sti", FACES_DIR, iId ); + } } } @@ -1943,10 +1965,29 @@ void DisplayPicturesOfCurrentTeam( void ) if ((50 < pSoldier->ubProfile) && (57 > pSoldier->ubProfile)) { sprintf( sTemp, "%s%03d.sti", SMALL_FACES_DIR, gMercProfiles[ pSoldier->ubProfile ].ubFaceIndex ); } else { - if ( pSoldier->ubProfile < 100 ) { - sprintf(sTemp, "%s%02d.sti", SMALL_FACES_DIR, pSoldier->ubProfile); - } else { - sprintf(sTemp, "%s%03d.sti", SMALL_FACES_DIR, pSoldier->ubProfile); + if ( pSoldier->ubProfile < 100 ) + { + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%02d.sti", SMALL_FACES_DIR, gMercProfiles[ pSoldier->ubProfile ].ubFaceIndex); + } + else + { + sprintf(sTemp, "%s%02d.sti", SMALL_FACES_DIR, pSoldier->ubProfile); + } + } + else + { + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%03d.sti", SMALL_FACES_DIR, gMercProfiles[ pSoldier->ubProfile ].ubFaceIndex); + } + else + { + sprintf(sTemp, "%s%03d.sti", SMALL_FACES_DIR, pSoldier->ubProfile); + } } // else } // else @@ -4462,10 +4503,29 @@ void DisplayPortraitOfPastMerc( INT32 iId , INT32 iCounter, BOOLEAN fDead, BOOLE if( ( 50 < iId )&&( 57 > iId ) ) { sprintf( sTemp, "%s%03d.sti", SMALL_FACES_DIR, gMercProfiles[ iId ].ubFaceIndex ); } else { - if( iId < 100 ) { - sprintf(sTemp, "%s%02d.sti", SMALL_FACES_DIR, iId ); - } else { - sprintf(sTemp, "%s%03d.sti", SMALL_FACES_DIR, iId ); + if( iId < 100 ) + { + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%02d.sti", SMALL_FACES_DIR, gMercProfiles[ iId ].ubFaceIndex ); + } + else + { + sprintf(sTemp, "%s%02d.sti", SMALL_FACES_DIR, iId ); + } + } + else + { + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Do not read direct profile number, instead, look inside the profile for a ubFaceIndex value. + sprintf(sTemp, "%s%03d.sti", SMALL_FACES_DIR, gMercProfiles[ iId ].ubFaceIndex ); + } + else + { + sprintf(sTemp, "%s%03d.sti", SMALL_FACES_DIR, iId ); + } } } diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index 81a78f80..888ae749 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -108,6 +108,10 @@ #include "postalservice.h" // HEADROCK HAM B1: Additional Include for HAM #include "MilitiaSquads.h" + // HEADROCK HAM 3.5: Another include for HAM + #include "Facilities.h" + // HEADROCK HAM 3.6: Yet another include, goddammit + #include "Town Militia.h" #endif #include "BobbyR.h" @@ -389,7 +393,20 @@ typedef struct INT8 fPlayerTeamSawJoey; INT8 fMikeShouldSayHi; - UINT8 ubFiller[550]; //This structure should be 1024 bytes + // HEADROCK HAM 3.6: New global variable that tracks money owed for facility use. + INT32 iTotalOwedForFacilityOperationsToday; + + // HEADROCK HAM 3.6: Now saving Skyrider Cost Modifier variable; + INT16 sSkyriderCostModifier; + + // HEADROCK HAM 3.6: New global variable indicating whether we owe cash for facility work. + BOOLEAN fOutstandingFacilityDebt; + + // HEADROCK HAM 3.6: Global variable keeping track of Militia Upkeep Costs at last midnight. + UINT32 uiTotalUpkeepForMilitia; + + // HEADROCK HAM 3.6: Removed 16 fillers (16 bytes) to accomodate the above new variables. + UINT8 ubFiller[534]; //This structure should be 1024 bytes } GENERAL_SAVE_INFO; @@ -4390,7 +4407,6 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) UpdateMercMercContractInfo(); } - if ( guiCurrentSaveGameVersion <= 89 ) { // ARM: A change was made in version 89 where refuel site availability now also depends on whether the player has @@ -4441,11 +4457,17 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) // fix squads CheckSquadMovementGroups(); // HEADROCK HAM B1: Re-Adjust Dynamic Roaming Militia restrictions - if (gGameExternalOptions.bDynamicRestrictRoaming) + if (gGameExternalOptions.fDynamicRestrictRoaming) { AdjustRoamingRestrictions(); } + // HEADROCK HAM 3.5: Tells the rest of the program whether we've got mercs working on detecting enemy units. + UpdateStrategicDetectionLevel( ); + + // HEADROCK HAM 3.5: recalculate a modifier to Skyrider's costs, based on mercs working in certain facilities. + UpdateSkyriderCostModifier( ); + //The above function LightSetBaseLevel adjusts ALL the level node light values including the merc node, //we must reset the values HandlePlayerTogglingLightEffects( FALSE ); @@ -6133,6 +6155,18 @@ BOOLEAN SaveGeneralInfo( HWFILE hFile ) sGeneralInfo.fPlayerTeamSawJoey = gfPlayerTeamSawJoey; sGeneralInfo.fMikeShouldSayHi = gfMikeShouldSayHi; + // HEADROCK HAM 3.6: Save new global variable for facility costs + sGeneralInfo.iTotalOwedForFacilityOperationsToday = giTotalOwedForFacilityOperationsToday; + + // HEADROCK HAM 3.6: Save Skyrider Costs Modifier + sGeneralInfo.sSkyriderCostModifier = gsSkyriderCostModifier; + + // HEADROCK HAM 3.6: Save new global variable for facility debt + sGeneralInfo.fOutstandingFacilityDebt = gfOutstandingFacilityDebt; + + // HEADROCK HAM 3.6: Save new global variable for militia upkeep + sGeneralInfo.uiTotalUpkeepForMilitia = guiTotalUpkeepForMilitia; + //Setup the //Save the current music mode FileWrite( hFile, &sGeneralInfo, sizeof( GENERAL_SAVE_INFO ), &uiNumBytesWritten ); @@ -6155,7 +6189,8 @@ BOOLEAN LoadGeneralInfo( HWFILE hFile ) GENERAL_SAVE_INFO sGeneralInfo; memset( &sGeneralInfo, 0, sizeof( GENERAL_SAVE_INFO ) ); - + // HEADROCK HAM 3.6: Hi. If you can see this comment, please delete the next line. Sorry! :) + INT32 blah = sizeof( GENERAL_SAVE_INFO ); //Load the current music mode FileRead( hFile, &sGeneralInfo, sizeof( GENERAL_SAVE_INFO ), &uiNumBytesRead ); @@ -6405,6 +6440,18 @@ BOOLEAN LoadGeneralInfo( HWFILE hFile ) gfPlayerTeamSawJoey = sGeneralInfo.fPlayerTeamSawJoey; gfMikeShouldSayHi = sGeneralInfo.fMikeShouldSayHi; + // HEADROCK HAM 3.6: Load new global variable for facility costs + giTotalOwedForFacilityOperationsToday = sGeneralInfo.iTotalOwedForFacilityOperationsToday; + + // HEADROCK HAM 3.6: Load Skyrider Costs Modifier + gsSkyriderCostModifier = sGeneralInfo.sSkyriderCostModifier; + + // HEADROCK HAM 3.6: Load new global variable for facility debt + gfOutstandingFacilityDebt = sGeneralInfo.fOutstandingFacilityDebt; + + // HEADROCK HAM 3.6: Load new global variable for militia upkeep + guiTotalUpkeepForMilitia = sGeneralInfo.uiTotalUpkeepForMilitia; + return( TRUE ); } diff --git a/Standard Gaming Platform/mousesystem.cpp b/Standard Gaming Platform/mousesystem.cpp index 511551fb..e4b90f60 100644 --- a/Standard Gaming Platform/mousesystem.cpp +++ b/Standard Gaming Platform/mousesystem.cpp @@ -1296,6 +1296,8 @@ INT16 GetNumberOfLinesInHeight( const STR16 pStringA ) { STR16 pToken; INT16 sCounter = 0; + // HEADROCK HAM 3.6: This is a serious limitation... Increasing size + //CHAR16 pString[ 512 ]; CHAR16 pString[ 4096 ]; wcscpy( pString, pStringA ); @@ -1305,16 +1307,14 @@ INT16 GetNumberOfLinesInHeight( const STR16 pStringA ) while( pToken != NULL ) { - // WANNE: Fix by Headrock + // HEADROCK HAM 3.6: Make sure that all lines can appear on screen. If impossible, reduce number of lines + // artificially. if ( (sCounter+1) * (GetFontHeight(FONT10ARIAL)+1) > (SCREEN_HEIGHT - 10) ) - { - break; - } - pToken = wcstok( NULL, L"\n" ); - sCounter++; - - /*pToken = wcstok( NULL, L"\n" ); - sCounter++;*/ + { + break; + } + pToken = wcstok( NULL, L"\n" ); + sCounter++; } return( sCounter ); @@ -1347,7 +1347,10 @@ void DisplayFastHelp( MOUSE_REGION *region ) if ( (iX + iW) >= SCREEN_WIDTH ) iX = (SCREEN_WIDTH - iW - 4); - iY = (INT32)region->RegionTopLeftY - (iH * 3 / 4); + // HEADROCK HAM 3.6: This formula causes the tooltip to appear UNDERNEATH the cursor if + // the tooltip's parent region is small. That makes reading the tooltip very difficult. Not desirable. + //iY = (INT32)region->RegionTopLeftY - (iH * 3 / 4); + iY = (INT32)region->RegionTopLeftY - iH; if (iY < 0) iY = 0; @@ -1384,6 +1387,8 @@ void DisplayFastHelp( MOUSE_REGION *region ) INT16 GetWidthOfString( const STR16 pStringA ) { + // HEADROCK HAM 3.6: This is a serious limitation... Increasing size. + //CHAR16 pString[ 512 ]; CHAR16 pString[ 4096 ]; STR16 pToken; INT16 sWidth = 0; @@ -1411,6 +1416,8 @@ void DisplayHelpTokenizedString( const STR16 pStringA, INT16 sX, INT16 sY ) STR16 pToken; INT32 iCounter = 0, i; UINT32 uiCursorXPos; + // HEADROCK HAM 3.6: This is a serious limitation... Increasing size + //CHAR16 pString[ 512 ]; CHAR16 pString[ 4096 ]; INT32 iLength; @@ -1421,15 +1428,14 @@ void DisplayHelpTokenizedString( const STR16 pStringA, INT16 sX, INT16 sY ) while( pToken != NULL ) { - // WANNE: Fix by Headrock + // HEADROCK HAM 3.6: If height of screen exceeds screen height, replace the last VISIBLE line with "..." + // and break the cycle. if ( (iCounter+2) * (GetFontHeight(FONT10ARIAL)+1) > (SCREEN_HEIGHT - 10) ) - { - mprintf( sX, sY + iCounter * (GetFontHeight(FONT10ARIAL)+1), L"..." ); - break; - } - iLength = (INT32)wcslen( pToken ); - - //iLength = (INT32)wcslen( pToken ); + { + mprintf( sX, sY + iCounter * (GetFontHeight(FONT10ARIAL)+1), L"..." ); + break; + } + iLength = (INT32)wcslen( pToken ); for( i = 0; i < iLength; i++ ) { uiCursorXPos = StringPixLengthArgFastHelp( FONT10ARIAL, FONT10ARIALBOLD, i, pToken ); diff --git a/Strategic/AI Viewer.cpp b/Strategic/AI Viewer.cpp index c5b4bcf7..c0fe5c23 100644 --- a/Strategic/AI Viewer.cpp +++ b/Strategic/AI Viewer.cpp @@ -1256,7 +1256,8 @@ void TestIncoming4SidesCallback( GUI_BUTTON *btn, INT32 reason ) UINT32 uiWorldMin; Compression0Callback( ButtonList[ iViewerButton[ COMPRESSION0 ] ], MSYS_CALLBACK_REASON_LBUTTON_UP ); if( ( gsSelSectorX == 0 ) || ( gsSelSectorY == 0 ) ) - gsSelSectorX = 9, gsSelSectorY = 1; + // HEADROCK HAM 3.5: Not sure what this function does at all... But this is externalized now anyway. + gsSelSectorX = gGameExternalOptions.ubDefaultArrivalSectorX, gsSelSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; ubSector = SECTOR( gsSelSectorX, gsSelSectorY ); uiWorldMin = GetWorldTotalMin(); diff --git a/Strategic/Assignments.cpp b/Strategic/Assignments.cpp index e7cf4248..39b033ec 100644 --- a/Strategic/Assignments.cpp +++ b/Strategic/Assignments.cpp @@ -68,6 +68,8 @@ class OBJECTTYPE; class SOLDIERTYPE; #include "MilitiaSquads.h" +// HEADROCK HAM 3.5: Include Facility data +#include "Facilities.h" // various reason an assignment can be aborted before completion enum{ @@ -146,6 +148,9 @@ INT32 ghAttributeBox = -1; INT32 ghRemoveMercAssignBox = -1; INT32 ghContractBox = -1; INT32 ghMoveBox = -1; +// HEADROCK HAM 3.6: Facility Menu, Submenu +INT32 ghFacilityBox = -1; +INT32 ghFacilityAssignmentBox = -1; //INT32 ghUpdateBox = -1; // the x,y position of assignment pop up in tactical @@ -161,9 +166,11 @@ MOUSE_REGION gContractMenuRegion[ MAX_CONTRACT_MENU_STRING_COUNT ]; MOUSE_REGION gRemoveMercAssignRegion[ MAX_REMOVE_MERC_COUNT ]; MOUSE_REGION gEpcMenuRegion[ MAX_EPC_MENU_STRING_COUNT ]; MOUSE_REGION gRepairMenuRegion[ 20 ]; - // mouse region for vehicle menu MOUSE_REGION gVehicleMenuRegion[ 20 ]; +// HEADROCK HAM 3.6: Facility Menu +MOUSE_REGION gFacilityMenuRegion[ MAX_NUM_FACILITY_TYPES ]; +MOUSE_REGION gFacilityAssignmentMenuRegion[ NUM_FACILITY_ASSIGNMENTS ]; MOUSE_REGION gAssignmentScreenMaskRegion; @@ -207,6 +214,11 @@ SOLDIERTYPE *gpDismissSoldier = NULL; BOOLEAN gfReEvaluateEveryonesNothingToDo = FALSE; +// HEADROCK HAM 3.6: Stored Facility worker (for callback purposes) +SOLDIERTYPE *gpFacilityStaffer; +// HEADROCK HAM 3.6: Current FacilityType whose Assignments are shown in the Sub-Menu +INT8 gubFacilityInSubmenu; +UINT8 gubFacilityLineForSubmenu; // Which line to highlight in the facility menu... /* // the amount time must be on assignment before it can have any effect #define MINUTES_FOR_ASSIGNMENT_TO_COUNT 45 @@ -381,6 +393,9 @@ void HandleShadingOfLinesForVehicleMenu( void ); void HandleShadingOfLinesForRepairMenu( void ); void HandleShadingOfLinesForTrainingMenu( void ); void HandleShadingOfLinesForAttributeMenus( void ); +// HEADROCK HAM 3.6: Shade Facility Box Lines +void HandleShadingOfLinesForFacilityMenu( void ); +void HandleShadingOfLinesForFacilityAssignmentMenu( void ); // post message about contract void PostContractMessage( SOLDIERTYPE *pCharacter, INT32 iContract ); @@ -390,12 +405,18 @@ void PostTerminateMessage( SOLDIERTYPE *pCharacter ); BOOLEAN DisplayVehicleMenu( SOLDIERTYPE *pSoldier ); BOOLEAN DisplayRepairMenu( SOLDIERTYPE *pSoldier ); +// HEADROCK HAM 3.6: Display Facility Menu. +BOOLEAN DisplayFacilityMenu( SOLDIERTYPE *pSoldier ); +BOOLEAN DisplayFacilityAssignmentMenu( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType ); // create menus void CreateEPCBox( void ); void CreateSquadBox( void ); void CreateVehicleBox(); void CreateRepairBox( void ); +// HEADROCK HAM 3.6: Facility Box. +void CreateFacilityBox( void ); +void CreateFacilityAssignmentBox( void ); /* // get how fast the person regains sleep @@ -428,6 +449,7 @@ UINT8 RepairRobot( SOLDIERTYPE *pRobot, UINT8 ubRepairPts, BOOLEAN *pfNothingLef UINT8 HandleRepairOfRobotBySoldier( SOLDIERTYPE *pSoldier, UINT8 ubRepairPts, BOOLEAN *pfNothingLeftToRepair ); BOOLEAN HandleAssignmentExpansionAndHighLightForAssignMenu( SOLDIERTYPE *pSoldier ); BOOLEAN HandleAssignmentExpansionAndHighLightForTrainingMenu( void ); +BOOLEAN HandleAssignmentExpansionAndHighLightForFacilityMenu ( void ); // Facility menu and submenu expansion BOOLEAN HandleShowingOfMovementBox( void ); //BOOLEAN HandleShowingOfUpBox( void ); void ReportTrainersTraineesWithoutPartners( void ); @@ -442,8 +464,12 @@ extern BOOLEAN CanChangeSleepStatusForCharSlot( INT8 bCharNumber ); extern UINT32 VirtualSoldierDressWound( SOLDIERTYPE *pSoldier, SOLDIERTYPE *pVictim, OBJECTTYPE *pKit, INT16 sKitPts, INT16 sStatus ); // only 2 trainers are allowed per sector, so this function counts the # in a guy's sector -INT8 CountMilitiaTrainersInSoldiersSector( SOLDIERTYPE * pSoldier ); - +// HEADROCK HAM 3.6: Now takes an extra argument for Militia Type +INT8 CountMilitiaTrainersInSoldiersSector( SOLDIERTYPE * pSoldier, UINT8 ubMilitiaType ); +// HEADROCK HAM 3.6: Check number of mercs currently staffing a specific facility. +INT8 CountFreeFacilitySlots( UINT8 sMapX, UINT8 sMapY, UINT8 ubFacilityType ); +// HEADROCK HAM 3.6: Check number of mercs currently staffing a specific facility AND performing the same assignment +INT8 CountFreeFacilityAssignmentSlots( UINT8 sMapX, UINT8 sMapY, UINT8 ubFacilityType, UINT8 ubAssignmentType ); // notify player of assignment attempt failure void NotifyPlayerOfAssignmentAttemptFailure( INT8 bAssignment ); @@ -464,6 +490,9 @@ BOOLEAN CanCharacterRepairAnotherSoldiersStuff( SOLDIERTYPE *pSoldier, SOLDIERTY // can this character EVER train militia? BOOLEAN BasicCanCharacterTrainMilitia( SOLDIERTYPE *pCharacter ); +BOOLEAN BasicCanCharacterTrainMobileMilitia( SOLDIERTYPE *pSoldier ); +// Can this character EVER work in any facility? +BOOLEAN BasicCanCharacterFacility( SOLDIERTYPE *pSoldier ); SOLDIERTYPE *GetSelectedAssignSoldier( BOOLEAN fNullOK ); @@ -483,6 +512,7 @@ BOOLEAN IsSoldierCloseEnoughToADoctor( SOLDIERTYPE *pPatient ); */ #ifdef JA2BETAVERSION +// HEADROCK HAM 3.6: Added argument for separation of Mobile/Town Militia void VerifyTownTrainingIsPaidFor( void ); #endif @@ -530,6 +560,9 @@ void ChangeSoldiersAssignment( SOLDIERTYPE *pSoldier, INT8 bAssignment ) pSoldier->flags.fFixingRobot = FALSE; pSoldier->bVehicleUnderRepairID = -1; + // HEADROCK HAM 3.6: Clean out new Facility Operation variable. + pSoldier->sFacilityTypeOperated = -1; + if ( ( bAssignment == DOCTOR ) || ( bAssignment == PATIENT ) || ( bAssignment == ASSIGNMENT_HOSPITAL ) ) { AddStrategicEvent( EVENT_BANDAGE_BLEEDING_MERCS, GetWorldTotalMin() + 1, 0 ); @@ -1034,12 +1067,12 @@ BOOLEAN CanCharacterPatient( SOLDIERTYPE *pSoldier ) } - +// This function tests whether this sector EVER allows Militia Training, and whether the character is at all capable +// of taking an assignment. BOOLEAN BasicCanCharacterTrainMilitia( SOLDIERTYPE *pSoldier ) { - // is the character capable of training a town? - // they must be alive/conscious and in the sector with the town - BOOLEAN fSamSitePresent = FALSE; + ///////////////////////////////////////////////////// + // Tests whether character can do assignments at all! AssertNotNIL(pSoldier); @@ -1048,39 +1081,46 @@ BOOLEAN BasicCanCharacterTrainMilitia( SOLDIERTYPE *pSoldier ) return( FALSE ); } - // make sure character is alive and conscious + // Is character dead or unconscious? if( pSoldier->stats.bLife < OKLIFE ) { // dead or unconscious... return ( FALSE ); } - // underground training is not allowed (code doesn't support and it's a reasonable enough limitation) + // Is character underground? if( pSoldier->bSectorZ != 0 ) { + // underground training is not allowed (code doesn't support and it's a reasonable enough limitation) return( FALSE ); } - // is there a town in the character's current sector? - if( StrategicMap[ CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ) ].bNameId == BLANK_SECTOR ) + // Is character on the way into/out of Arulco? + if( IsCharacterInTransit( pSoldier ) == TRUE ) { - fSamSitePresent = IsThisSectorASAMSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); - - // check if sam site - if( fSamSitePresent == FALSE ) - { - // nope - return ( FALSE ); - } + return ( FALSE ); } - if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + // Is character travelling between sectors? + if( CharacterIsBetweenSectors( pSoldier ) ) { return( FALSE ); } + // Is character an Escortee? + if( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__EPC ) + { + // epcs can't do this + return( FALSE ); + } - // check in helicopter in hostile sector + // Is character a Vehicle or Robot? + if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || AM_A_ROBOT( pSoldier ) ) + { + return( FALSE ); + } + + // IS character inside a helicopter over a hostile sector? if( pSoldier->bAssignment == VEHICLE ) { if( ( iHelicopterVehicleId != -1 ) && ( pSoldier->iVehicleId == iHelicopterVehicleId ) ) @@ -1093,86 +1133,161 @@ BOOLEAN BasicCanCharacterTrainMilitia( SOLDIERTYPE *pSoldier ) } } - // in transit? - if( IsCharacterInTransit( pSoldier ) == TRUE ) + //////////////////////////////////////////////////////////////////////// + // Tests to see whether this sector allows training militia for ANYBODY. + + // is there a SAM Site in the character's current sector? + if( StrategicMap[ CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ) ].bNameId == BLANK_SECTOR ) { + BOOLEAN fSamSitePresent = IsThisSectorASAMSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ); + + // check if sam site + if( fSamSitePresent == FALSE ) + { + // nope + return ( FALSE ); + } + } + else + { + // There's a city here. Does it allow training militia? + INT8 bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + if (!MilitiaTrainingAllowedInTown( bTownId )) + { + // City does not allow militia training at all. + return ( FALSE ); + } + } + + // HEADROCK HAM 3.5: Only facilities allow militia training, and determine how many trainers can work here. + // Does sector have at least one facility that allows training? + BOOLEAN fMilitiaTrainingAllowed = FALSE; + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + // Is this facility here? + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + // Does it allow training militia? + if (gFacilityTypes[cnt].ubMilitiaTrainersAllowed) + { + // Cool. + fMilitiaTrainingAllowed = TRUE; + } + } + } + if (!fMilitiaTrainingAllowed) + { + // Militia training NOT allowed here! return ( FALSE ); } - // character on the move? - if( CharacterIsBetweenSectors( pSoldier ) ) + return ( TRUE ); +} + +// Determines whether the character has the required condition to train Militia at this time. +// The conditions tested in this function might change WHILE THE CHARACTER IS ALREADY TRAINING MILITIA, which is +// how this function is normally different from "BasicCan...". +BOOLEAN CanCharacterTrainMilitia( SOLDIERTYPE *pSoldier ) +{ + AssertNotNIL(pSoldier); + + // Make sure the basic sector/merc variables are still applicable. This is simply a fail-safe. + if( !BasicCanCharacterTrainMilitia( pSoldier ) ) + { + // Soldier/Sector have somehow failed the basic test. Character automatically fails this test as well. + return( FALSE ); + } + + if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) { return( FALSE ); } - if( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__EPC ) - { - // epcs can't do this - return( FALSE ); - } - - if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || AM_A_ROBOT( pSoldier ) ) - { - return( FALSE ); - } - - // has leadership skill? + // Has leadership skill? if( pSoldier->stats.bLeadership <= 0 ) { // no skill whatsoever return ( FALSE ); } + // Sector Loyalty above minimum? + if( !DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) ) + { + // Not enough Loyalty... + return ( FALSE ); + } - // can train militia + // HEADROCK HAM 3: When "Minimum Leadership for Militia Training" is enforced, this value holds the + // merc's effective leadership, after the "TEACHER" trait is taken into account. + UINT16 usEffectiveLeadership; + + // HEADROCK HAM 3: Determine whether the merc has enough leadership to train militia. The teacher trait may + // increase or decrease the effective skill. + if( gGameExternalOptions.ubMinimumLeadershipToTrainMilitia > 0 ) + { + // Read BASE leadership + usEffectiveLeadership = pSoldier->stats.bLeadership; + + // Apply modifier for TEACHER trait, if that feature is activated + if ( gGameExternalOptions.usTeacherTraitEffectOnLeadership > 0 && gGameExternalOptions.usTeacherTraitEffectOnLeadership != 100 ) + { + // Modifier applied once for each TEACHING level. + for (UINT8 i = 0; i < NUM_SKILL_TRAITS( pSoldier, TEACHING ); i++ ) + { + // This is a percentage modifier. + usEffectiveLeadership = (usEffectiveLeadership * gGameExternalOptions.usTeacherTraitEffectOnLeadership)/100; + } + } + + usEffectiveLeadership = __min(100,usEffectiveLeadership); + + // Is leadership too low to proceed? + if (usEffectiveLeadership < gGameExternalOptions.ubMinimumLeadershipToTrainMilitia) + { + return ( FALSE ); + } + } + + ////////////////////////////////////////////// + // HEADROCK HAM 3.5: Militia Training Facility + // + + // Militia training is enabled in the sector only if there is a facility that allows this here. + // If one or more facilities are found, positive values are summed up and presented as the number + // of trainers allowed in the sector. Values are read from XML, and can be set to mimic JA2 + // defaults. This renders the INI setting "MAX_MILITIA_TRAINERS.." obsolete. + // HEADROCK HAM 3.5: Only facilities allow militia training, and determine how many trainers can work here. + // Does sector have at least one facility that allows training? + UINT8 ubFacilityTrainersAllowed = 0; + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + // Is this facility here? + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + // Increase tally + ubFacilityTrainersAllowed += gFacilityTypes[cnt].ubMilitiaTrainersAllowed; + } + } + + // Count number of trainers already operating here + if ( CountMilitiaTrainersInSoldiersSector( pSoldier, TOWN_MILITIA ) >= ubFacilityTrainersAllowed ) + { + // Too many trainers in sector. + return (FALSE); + } + + // Is town full of Elites? + if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) + { + // Town is full of Elites. No further training required. + // Also note that this takes care of Regulars as well, if Elite training is disabled. + return( FALSE ); + } + + // If we've reached this, then all is well. return( TRUE ); } -// Kaiden: Roaming Militia Training: -// Commenting out the check for: -// IsMilitiaTrainableFromSoldiersSectorMaxed( -// At this point, we don't care if it's full yet - -BOOLEAN CanCharacterTrainMilitia( SOLDIERTYPE *pSoldier ) -{ - AssertNotNIL(pSoldier); - - if (gGameExternalOptions.gfmusttrainroaming - && (GetWorldDay( ) >= gGameExternalOptions.guiAllowMilitiaGroupsDelay) - && (!IsThisSectorASAMSector(pSoldier->sSectorX,pSoldier->sSectorY,0 ))) - { - if( BasicCanCharacterTrainMilitia( pSoldier ) && - MilitiaTrainingAllowedInSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) && - DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) && - /*( IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier ) == FALSE ) && */ - ( CountMilitiaTrainersInSoldiersSector( pSoldier ) < gGameExternalOptions.ubMaxMilitiaTrainersPerSector ) ) - { - return( TRUE ); - } - else - { - return( FALSE ); - } - } - else - { - if( BasicCanCharacterTrainMilitia( pSoldier ) && - MilitiaTrainingAllowedInSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) && - DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) && - ( IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier,ELITE_MILITIA ) == FALSE ) && - ( CountMilitiaTrainersInSoldiersSector( pSoldier ) < gGameExternalOptions.ubMaxMilitiaTrainersPerSector ) ) - { - return( TRUE ); - } - else - { - return( FALSE ); - } - } -} - - - BOOLEAN DoesTownHaveRatingToTrainMilitia( INT8 bTownId ) { DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Assignments1"); @@ -1228,7 +1343,7 @@ BOOLEAN DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( SOLDIERTYPE *pSol -INT8 CountMilitiaTrainersInSoldiersSector( SOLDIERTYPE * pSoldier ) +INT8 CountMilitiaTrainersInSoldiersSector( SOLDIERTYPE * pSoldier, UINT8 ubMilitiaType ) { INT8 bLoop; SOLDIERTYPE * pOtherSoldier; @@ -1241,7 +1356,12 @@ INT8 CountMilitiaTrainersInSoldiersSector( SOLDIERTYPE * pSoldier ) pOtherSoldier = MercPtrs[ bLoop ]; if ( pSoldier != pOtherSoldier && pOtherSoldier->bActive && pOtherSoldier->stats.bLife >= OKLIFE && pOtherSoldier->sSectorX == pSoldier->sSectorX && pOtherSoldier->sSectorY == pSoldier->sSectorY && pSoldier->bSectorZ == pOtherSoldier->bSectorZ ) { - if (pOtherSoldier->bAssignment == TRAIN_TOWN ) + // Count depends on Militia Type requested + if (ubMilitiaType == TOWN_MILITIA && pOtherSoldier->bAssignment == TRAIN_TOWN ) + { + bCount++; + } + else if (ubMilitiaType == MOBILE_MILITIA && pOtherSoldier->bAssignment == TRAIN_MOBILE ) { bCount++; } @@ -1743,7 +1863,9 @@ BOOLEAN CanCharacterSleep( SOLDIERTYPE *pSoldier, BOOLEAN fExplainWhyNot ) // not tired? - if( pSoldier->bBreathMax >= BREATHMAX_FULLY_RESTED ) + // HEADROCK HAM 3.5: Facilities can now reduce the maximum fatigue. + + if( pSoldier->bBreathMax >= __min(BREATHMAX_FULLY_RESTED, GetSectorModifier( pSoldier, FACILITY_MAX_BREATH ) ) ) { if( fExplainWhyNot ) { @@ -2000,6 +2122,9 @@ void UpdateAssignments() VerifyTownTrainingIsPaidFor(); #endif + // HEADROCK HAM 3.6: See what effect, if any, our Facility Staffers have on global variables. + UpdateGlobalVariablesFromFacilities(); + // run through sectors and handle each type in sector for(sX = 0 ; sX < MAP_WORLD_X; sX++ ) { @@ -2068,6 +2193,7 @@ void VerifyTownTrainingIsPaidFor( void ) pSoldier = &Menptr[ gCharactersList[ iCounter ].usSolID ]; + // HEADROCK HAM 3.6: New argument controls testing for Mobiles/Garrisons separately. if( pSoldier->bActive && ( pSoldier->bAssignment == TRAIN_TOWN ) ) { // make sure that sector is paid up! @@ -2075,11 +2201,22 @@ void VerifyTownTrainingIsPaidFor( void ) { // NOPE! We've got a bug somewhere StopTimeCompression(); - - // report the error + // report the error DoScreenIndependantMessageBox( L"ERROR: Unpaid militia training. Describe *how* you're re-assigning mercs, how many/where/when! Send *prior* save!", MSG_BOX_FLAG_OK, NULL ); - - // avoid repeating this + // avoid repeating this + break; + } + } + if( pSoldier->bActive && ( pSoldier->bAssignment == TRAIN_MOBILE ) ) + { + // make sure that sector is paid up! + if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMobileMilitiaTrainingPaid == FALSE ) + { + // NOPE! We've got a bug somewhere + StopTimeCompression(); + // report the error + DoScreenIndependantMessageBox( L"ERROR: Unpaid militia training. Describe *how* you're re-assigning mercs, how many/where/when! Send *prior* save!", MSG_BOX_FLAG_OK, NULL ); + // avoid repeating this break; } } @@ -2175,6 +2312,8 @@ UINT16 CalculateHealingPointsForDoctor(SOLDIERTYPE *pDoctor, UINT16 *pusMaxPts, UINT16 usKitPts = 0; INT8 bMedFactor; + INT16 sSectorModifier = 100; + AssertNotNIL(pDoctor); AssertNotNIL(pusMaxPts); @@ -2187,12 +2326,22 @@ UINT16 CalculateHealingPointsForDoctor(SOLDIERTYPE *pDoctor, UINT16 *pusMaxPts, } } + // HEADROCK HAM 3.5: Read bonus directly from Sector Facility info + if (pDoctor->bSectorZ == 0 && + GetSoldierFacilityAssignmentIndex(pDoctor) == FAC_DOCTOR ) + { + // Read percentage modifier from the facility in question, including ambient effects. + sSectorModifier = GetSectorModifier( pDoctor, FACILITY_PERFORMANCE_MOD ); + } + // calculate effective doctoring rate (adjusted for drugs, alcohol, etc.) usHealPts = (UINT16) (( EffectiveMedical( pDoctor ) * (( EffectiveDexterity( pDoctor ) + EffectiveWisdom( pDoctor ) ) / 2) * (100 + ( 5 * EffectiveExpLevel( pDoctor) ) ) ) / gGameExternalOptions.ubDoctoringRateDivisor); + usHealPts = __max(0, (usHealPts * sSectorModifier)/100); // calculate normal doctoring rate - what it would be if his stats were "normal" (ignoring drugs, fatigue, equipment condition) // and equipment was not a hindrance *pusMaxPts = ( pDoctor->stats.bMedical * (( pDoctor->stats.bDexterity + pDoctor->stats.bWisdom ) / 2 ) * (100 + ( 5 * pDoctor->stats.bExpLevel) ) ) / gGameExternalOptions.ubDoctoringRateDivisor; + *pusMaxPts = __max(0,*pusMaxPts+((*pusMaxPts * sSectorModifier)/100)); // adjust for fatigue ReducePointsForFatigue( pDoctor, &usHealPts ); @@ -2232,6 +2381,8 @@ UINT8 CalculateRepairPointsForRepairman(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts UINT16 usRepairPts; UINT16 usKitPts; UINT8 ubKitEffectiveness; + // HEADROCK HAM 3.5: Modifier from local facilities + INT16 sSectorModifier = 100; // make sure toolkit in hand? if( fMakeSureKitIsInHand ) @@ -2276,6 +2427,18 @@ UINT8 CalculateRepairPointsForRepairman(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts ubKitEffectiveness = 100; } + if (pSoldier->bSectorZ == 0) + { + if (GetSoldierFacilityAssignmentIndex( pSoldier ) == FAC_REPAIR_ITEMS || + GetSoldierFacilityAssignmentIndex( pSoldier ) == FAC_REPAIR_VEHICLE || + GetSoldierFacilityAssignmentIndex( pSoldier ) == FAC_REPAIR_ROBOT) + { + // HEADROCK HAM 3.5: Read bonus directly from Sector Facility info + sSectorModifier = GetSectorModifier(pSoldier, FACILITY_PERFORMANCE_MOD); + } + } + usRepairPts = __max(0, (usRepairPts * sSectorModifier)/100); + // adjust for equipment usRepairPts = (usRepairPts * ubKitEffectiveness) / 100; @@ -3254,6 +3417,8 @@ void HandleRepairBySoldier( SOLDIERTYPE *pSoldier ) BOOLEAN fAnyOfSoldiersOwnItemsWereFixed = FALSE; OBJECTTYPE * pObj; + UINT16 usKitDegrade = 100; + // grab max number of repair pts open to this soldier ubRepairPtsLeft = CalculateRepairPointsForRepairman( pSoldier, &usMax, TRUE ); @@ -3385,8 +3550,14 @@ void HandleRepairBySoldier( SOLDIERTYPE *pSoldier ) StatChange( pSoldier, MECHANAMT, ( UINT16 ) (ubRepairPtsUsed / 2), FALSE ); StatChange( pSoldier, DEXTAMT, ( UINT16 ) (ubRepairPtsUsed / 2), FALSE ); + // HEADROCK HAM 3.6: Facilities can change the speed of kit degrade. + if (pSoldier->bSectorZ == 0) + { + usKitDegrade = GetSectorModifier( pSoldier, FACILITY_KIT_DEGRADE_MOD ); + } + // check if kit damaged/depleted - if( ( Random( 100 ) ) < (UINT32) (ubRepairPtsUsed * 5) ) // CJC: added a x5 as this wasn't going down anywhere fast enough + if( ( Random( 100 ) ) < (UINT32) __max(1,((ubRepairPtsUsed * 5 * usKitDegrade) / 100)) ) // CJC: added a x5 as this wasn't going down anywhere fast enough { // kit item damaged/depleted, burn up points of toolkit..which is in right hand UseKitPoints( &( pSoldier->inv[ HANDPOS ] ), 1, pSoldier ); @@ -3438,8 +3609,8 @@ BOOLEAN IsItemRepairable( UINT16 usItem, INT16 bStatus ) return ( FALSE ); } - -void HandleRestAndFatigueInSector( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) +// Not used anywhere?! +/*void HandleRestAndFatigueInSector( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) { // this will handle all sleeping characters in this sector SOLDIERTYPE *pSoldier, *pTeamSoldier; @@ -3457,7 +3628,7 @@ void HandleRestAndFatigueInSector( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) } } } -} +}*/ /* @@ -3487,8 +3658,34 @@ void RestCharacter( SOLDIERTYPE *pSoldier ) { // handle the sleep of this character, update bBreathMax based on sleep they have INT8 bMaxBreathRegain = 0; + INT16 sSectorModifier = 100; + INT8 bDivisor = 0; - bMaxBreathRegain = 50 / CalcSoldierNeedForSleep( pSoldier ); + // Determine how many hours a day this merc must sleep. Normally this would range between 6 and 12 hours. + // Injuries and/or martial arts trait can change the limits to between 4 and 18 hours a day. + bDivisor = CalcSoldierNeedForSleep( pSoldier ); + + // HEADROCK HAM 3.6: + // Night ops specialists sleep better during the day. Others sleep better during the night. + if (NightTime()) + { + bDivisor += 4-(2*NUM_SKILL_TRAITS( pSoldier, NIGHTOPS )); + } + else + { + bDivisor += (2*NUM_SKILL_TRAITS( pSoldier, NIGHTOPS )); + } + + // Re-enforce limits + bDivisor = __min(18, __max(4, bDivisor)); + + bMaxBreathRegain = 50 / bDivisor; + + // Limit so that characters can't regain faster than 4 hours, ever + if (bMaxBreathRegain > 12) + { + bMaxBreathRegain = 12; + } // if breath max is below the "really tired" threshold if( pSoldier->bBreathMax < BREATHMAX_PRETTY_TIRED ) @@ -3497,6 +3694,23 @@ void RestCharacter( SOLDIERTYPE *pSoldier ) bMaxBreathRegain = ( bMaxBreathRegain * 3 / 2 ); } + // HEADROCK HAM 3.5: Read adjustment from local sector facilities + if (pSoldier->bSectorZ == 0) + { + if (pSoldier->flags.fMercAsleep) + { + sSectorModifier = GetSectorModifier( pSoldier, FACILITY_SLEEP_MOD ); + } + else + { + // Resting can be done at a facility now, and the program will automatically apply a performance bonus + // to this if the facility has one. If the character is simply resting ("On Duty", assigned to a squad), + // then only Ambient effects take place. + sSectorModifier = GetSectorModifier( pSoldier, FACILITY_PERFORMANCE_MOD ); + } + bMaxBreathRegain = (bMaxBreathRegain * sSectorModifier) / 100; + } + pSoldier->bBreathMax += bMaxBreathRegain; @@ -3527,6 +3741,7 @@ void FatigueCharacter( SOLDIERTYPE *pSoldier ) INT32 iPercentEncumbrance; INT32 iBreathLoss; INT8 bMaxBreathLoss = 0, bDivisor = 1; + INT16 sSectorModifier = 100; // vehicle or robot? @@ -3547,13 +3762,31 @@ void FatigueCharacter( SOLDIERTYPE *pSoldier ) return; } - + // Determine how many hours a day this merc can operate. Normally this would range between 12 and 18 hours. + // Injuries and/or martial arts trait can change the limits to between 6 and 20 hours a day. bDivisor = 24 - CalcSoldierNeedForSleep( pSoldier ); + + // HEADROCK HAM 3.6: + // Night ops specialists tire faster during the day. Others tire faster during the night. + if (NightTime()) + { + bDivisor -= 4-(2*NUM_SKILL_TRAITS( pSoldier, NIGHTOPS )); + } + else + { + bDivisor -= (2*NUM_SKILL_TRAITS( pSoldier, NIGHTOPS )); + } + + // Re-enforce limits + bDivisor = __min(20, __max(6, bDivisor)); + bMaxBreathLoss = 50 / bDivisor; - if( bMaxBreathLoss < 2 ) + // HEADROCK HAM 3.6: And make sure we allow no more than 18 hours of straight + // work. (Actually, 16, but who's counting) + if( bMaxBreathLoss < 3 ) { - bMaxBreathLoss = 2; + bMaxBreathLoss = 3; } //KM: Added encumbrance calculation to soldiers moving on foot. Anything above 100% will increase @@ -3575,6 +3808,13 @@ void FatigueCharacter( SOLDIERTYPE *pSoldier ) bMaxBreathLoss = ( bMaxBreathLoss * 3 / 2 ); } + // HEADROCK HAM 3.5: Read adjustment from local sector facilities + if (pSoldier->bSectorZ == 0) + { + sSectorModifier = GetSectorModifier( pSoldier, FACILITY_FATIGUE_MOD ); + bMaxBreathLoss = (bMaxBreathLoss * sSectorModifier) / 100; + } + pSoldier->bBreathMax -= bMaxBreathLoss; @@ -3603,7 +3843,6 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) SOLDIERTYPE *pTrainer; SOLDIERTYPE *pStudent; UINT8 ubStat; - BOOLEAN fAtGunRange = FALSE; UINT32 uiCnt=0; INT16 sTotalTrainingPts = 0; INT16 sTrainingPtsDueToInstructor = 0; @@ -3636,12 +3875,6 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) return; } - // are we training in the sector with gun range in Alma? - if ( (sMapX == GUN_RANGE_X) && (sMapY == GUN_RANGE_Y) && (bZ == GUN_RANGE_Z) ) - { - fAtGunRange = TRUE; - } - // init trainer list memset( pStatTrainerList, 0, sizeof( pStatTrainerList ) ); @@ -3663,7 +3896,7 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) // if he's training teammates in this stat if( ( pTrainer->bAssignment == TRAIN_TEAMMATE ) && ( pTrainer->bTrainStat == ubStat) && ( EnoughTimeOnAssignment( pTrainer ) ) && ( pTrainer->flags.fMercAsleep == FALSE ) ) { - sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, NULL, ubStat, fAtGunRange, &usMaxPts ); + sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, NULL, ubStat, &usMaxPts ); // if he's the best trainer so far for this stat if (sTrainingPtsDueToInstructor > sBestTrainingPts) @@ -3690,7 +3923,7 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) if ( EnoughTimeOnAssignment( pStudent ) && ( pStudent->flags.fMercAsleep == FALSE ) ) { // figure out how much the grunt can learn in one training period - sTotalTrainingPts = GetSoldierTrainingPts( pStudent, pStudent->bTrainStat, fAtGunRange, &usMaxPts ); + sTotalTrainingPts = GetSoldierTrainingPts( pStudent, pStudent->bTrainStat, &usMaxPts ); // if he's getting help if ( pStudent->bAssignment == TRAIN_BY_OTHER ) @@ -3710,7 +3943,7 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) //if ( EnoughTimeOnAssignment( pTrainer ) ) { // valid trainer is available, this gives the student a large training bonus! - sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, pStudent, pStudent->bTrainStat, fAtGunRange, &usMaxPts ); + sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, pStudent, pStudent->bTrainStat, &usMaxPts ); StatChange(pTrainer,LDRAMT,sTrainingPtsDueToInstructor,FALSE); StatChange(pTrainer,WISDOMAMT,sTrainingPtsDueToInstructor,FALSE); @@ -3730,8 +3963,11 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) } else if ( pTrainer != NULL ) { - // This only occurs if at least one trainer is awake. - TrainSoldierWithPts( pStudent, sTotalTrainingPts ); + if ( pTrainer->flags.fMercAsleep == FALSE ) + { + // This only occurs if at least one trainer is awake. + TrainSoldierWithPts( pStudent, sTotalTrainingPts ); + } } } } @@ -3751,7 +3987,8 @@ void HandleTrainingInSector( INT16 sMapX, INT16 sMapY, INT8 bZ ) { if( pTrainer->bActive && ( pTrainer->sSectorX == sMapX ) && ( pTrainer->sSectorY == sMapY ) && ( pTrainer->bSectorZ == bZ ) ) { - if( ( pTrainer->bAssignment == TRAIN_TOWN ) && ( EnoughTimeOnAssignment( pTrainer ) ) && ( pTrainer->flags.fMercAsleep == FALSE ) ) + // HEADROCK HAM 3.6: TRAIN_MOBILE also possible now. Handled the same way. + if( ( pTrainer->bAssignment == TRAIN_TOWN || pTrainer->bAssignment == TRAIN_MOBILE ) && ( EnoughTimeOnAssignment( pTrainer ) ) && ( pTrainer->flags.fMercAsleep == FALSE ) ) { sTownTrainingPts = GetTownTrainPtsForCharacter( pTrainer, &usMaxPts ); @@ -3816,7 +4053,7 @@ int TownTrainerQsortCompare(const void *pArg1, const void *pArg2) } -INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE *pStudent, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16 *pusMaxPts ) +INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE *pStudent, INT8 bTrainStat, UINT16 *pusMaxPts ) { // return the bonus training pts of this instructor with this student,...if student null, simply assignment student skill of 0 and student wisdom of 100 INT16 sTrainingPts = 0; @@ -3994,13 +4231,15 @@ INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE *pusMaxPts += ( ( ( bTrainingBonus + bOpinionFactor ) * *pusMaxPts ) / 100 ); // get special bonus if we're training marksmanship and we're in the gun range sector in Alma - if ( ( bTrainStat == MARKSMANSHIP ) && fAtGunRange ) + // HEADROCK HAM 3.5: Now reads from XML facilities, and works for all stats. + UINT8 bFacilityModifier = 100; + if ( pInstructor->bSectorZ == 0 ) { - bTrainingBonus += gGameExternalOptions.ubGunRangeTrainingBonus; + bFacilityModifier = (UINT8)GetSectorModifier( pInstructor, FACILITY_PERFORMANCE_MOD ); } // adjust for any training bonuses and for the relationship - sTrainingPts += ( ( ( bTrainingBonus + bOpinionFactor ) * sTrainingPts ) / 100 ); + sTrainingPts += ( ( ( bTrainingBonus + (bFacilityModifier-100) + bOpinionFactor ) * sTrainingPts ) / 100 ); // adjust for instructor fatigue ReducePointsForFatigue( pInstructor, (UINT16 *)&sTrainingPts ); @@ -4009,7 +4248,7 @@ INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE } -INT16 GetSoldierTrainingPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16 *pusMaxPts ) +INT16 GetSoldierTrainingPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, UINT16 *pusMaxPts ) { INT16 sTrainingPts = 0; INT16 bTrainingBonus = 0; @@ -4071,9 +4310,10 @@ INT16 GetSoldierTrainingPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAt sTrainingPts = __max( ( ( EffectiveWisdom( pSoldier ) * ( gGameExternalOptions.ubTrainingSkillMax - bSkill ) ) / gGameExternalOptions.ubSelfTrainingDivisor ), 1 ); // get special bonus if we're training marksmanship and we're in the gun range sector in Alma - if ( ( bTrainStat == MARKSMANSHIP ) && fAtGunRange ) + // HEADROCK HAM 3.5: Now reads from XML facilities, and works for all stats. + if ( pSoldier->bSectorZ == 0 ) { - bTrainingBonus += gGameExternalOptions.ubGunRangeTrainingBonus; + bTrainingBonus += (GetSectorModifier( pSoldier, FACILITY_PERFORMANCE_MOD ) - 100 ); } // adjust for any training bonuses @@ -4085,7 +4325,7 @@ INT16 GetSoldierTrainingPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAt return( sTrainingPts ); } -INT16 GetSoldierStudentPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16 *pusMaxPts ) +INT16 GetSoldierStudentPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, UINT16 *pusMaxPts ) { INT16 sTrainingPts = 0; INT16 bTrainingBonus = 0; @@ -4152,9 +4392,10 @@ INT16 GetSoldierStudentPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAtG sTrainingPts = __max( ( ( EffectiveWisdom( pSoldier ) * ( gGameExternalOptions.ubTrainingSkillMax - bSkill ) ) / gGameExternalOptions.ubSelfTrainingDivisor ), 1 ); // get special bonus if we're training marksmanship and we're in the gun range sector in Alma - if ( ( bTrainStat == MARKSMANSHIP ) && fAtGunRange ) + // HEADROCK HAM 3.5: Now reads from XML facilities, and works for all stats. + if ( pSoldier->bSectorZ == 0 ) { - bTrainingBonus += gGameExternalOptions.ubGunRangeTrainingBonus; + bTrainingBonus += ( GetSectorModifier( pSoldier, FACILITY_PERFORMANCE_MOD ) - 100 ); } // adjust for any training bonuses @@ -4178,7 +4419,7 @@ INT16 GetSoldierStudentPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAtG // NB skip the EnoughTime requirement to display what the value should be even if haven't been training long yet... if ( ( pTrainer->bAssignment == TRAIN_TEAMMATE ) && ( pTrainer->bTrainStat == bTrainStat) && ( pTrainer->flags.fMercAsleep == FALSE ) ) { - sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, pSoldier, bTrainStat, fAtGunRange, &usMaxTrainerPts ); + sTrainingPtsDueToInstructor = GetBonusTrainingPtsDueToInstructor( pTrainer, pSoldier, bTrainStat, &usMaxTrainerPts ); // if he's the best trainer so far for this stat if (sTrainingPtsDueToInstructor > sBestTrainingPts) @@ -4289,42 +4530,70 @@ BOOLEAN TrainTownInSector( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMapY, INT1 // increase town's training completed percentage - pSectorInfo->ubMilitiaTrainingPercentDone += (sTrainingPts / 100); - pSectorInfo->ubMilitiaTrainingHundredths += (sTrainingPts % 100); + // HEADROCK HAM 3.6: New town data for training Mobile Militia. + if (pTrainer->bAssignment == TRAIN_TOWN) + { + pSectorInfo->ubMilitiaTrainingPercentDone += (sTrainingPts / 100); + pSectorInfo->ubMilitiaTrainingHundredths += (sTrainingPts % 100); - if (pSectorInfo->ubMilitiaTrainingHundredths >= 100) + if (pSectorInfo->ubMilitiaTrainingHundredths >= 100) + { + pSectorInfo->ubMilitiaTrainingPercentDone++; + pSectorInfo->ubMilitiaTrainingHundredths -= 100; + } + + // NOTE: Leave this at 100, change TOWN_TRAINING_RATE if necessary. This value gets reported to player as a %age! + if( pSectorInfo->ubMilitiaTrainingPercentDone >= 100 ) + { + // zero out training completion - there's no carryover to the next training session + pSectorInfo->ubMilitiaTrainingPercentDone = 0; + pSectorInfo->ubMilitiaTrainingHundredths = 0; + + // make the player pay again next time he wants to train here + pSectorInfo->fMilitiaTrainingPaid = FALSE; + + TownMilitiaTrainingCompleted( pTrainer, sMapX, sMapY ); + + // training done + return( TRUE ); + } + } + else if (pTrainer->bAssignment == TRAIN_MOBILE) { - pSectorInfo->ubMilitiaTrainingPercentDone++; - pSectorInfo->ubMilitiaTrainingHundredths -= 100; + pSectorInfo->ubMobileMilitiaTrainingPercentDone += (sTrainingPts / 100); + pSectorInfo->ubMobileMilitiaTrainingHundredths += (sTrainingPts % 100); + + if (pSectorInfo->ubMobileMilitiaTrainingHundredths >= 100) + { + pSectorInfo->ubMobileMilitiaTrainingPercentDone++; + pSectorInfo->ubMobileMilitiaTrainingHundredths -= 100; + } + // NOTE: Leave this at 100, change TOWN_TRAINING_RATE if necessary. This value gets reported to player as a %age! + if( pSectorInfo->ubMobileMilitiaTrainingPercentDone >= 100 ) + { + // zero out training completion - there's no carryover to the next training session + pSectorInfo->ubMobileMilitiaTrainingPercentDone = 0; + pSectorInfo->ubMobileMilitiaTrainingHundredths = 0; + + // make the player pay again next time he wants to train here + pSectorInfo->fMobileMilitiaTrainingPaid = FALSE; + + TownMilitiaTrainingCompleted( pTrainer, sMapX, sMapY ); + + // training done + return( TRUE ); + } } - // NOTE: Leave this at 100, change TOWN_TRAINING_RATE if necessary. This value gets reported to player as a %age! - if( pSectorInfo->ubMilitiaTrainingPercentDone >= 100 ) - { - // zero out training completion - there's no carryover to the next training session - pSectorInfo->ubMilitiaTrainingPercentDone = 0; - pSectorInfo->ubMilitiaTrainingHundredths = 0; + return ( FALSE ); - // make the player pay again next time he wants to train here - pSectorInfo->fMilitiaTrainingPaid = FALSE; - - TownMilitiaTrainingCompleted( pTrainer, sMapX, sMapY ); - - // training done - return( TRUE ); - } - else - { - // not done - return ( FALSE ); - } } INT16 GetTownTrainPtsForCharacter( SOLDIERTYPE *pTrainer, UINT16 *pusMaxPts ) { INT16 sTotalTrainingPts = 0; - INT16 bTrainingBonus = 0; + INT16 sTrainingBonus = 0; // UINT8 ubTownId = 0; // calculate normal training pts - what it would be if his stats were "normal" (ignoring drugs, fatigue) @@ -4337,23 +4606,42 @@ INT16 GetTownTrainPtsForCharacter( SOLDIERTYPE *pTrainer, UINT16 *pusMaxPts ) // check for teaching bonuses if( gMercProfiles[ pTrainer->ubProfile ].bSkillTrait == TEACHING ) { - bTrainingBonus += gGameExternalOptions.ubTeachBonusToTrain; + sTrainingBonus += gGameExternalOptions.ubTeachBonusToTrain; } if( gMercProfiles[ pTrainer->ubProfile ].bSkillTrait2 == TEACHING ) { - bTrainingBonus += gGameExternalOptions.ubTeachBonusToTrain; + sTrainingBonus += gGameExternalOptions.ubTeachBonusToTrain; } // RPCs get a small training bonus for being more familiar with the locals and their customs/needs than outsiders if( pTrainer->ubProfile >= FIRST_RPC ) { - bTrainingBonus += gGameExternalOptions.ubRpcBonusToTrainMilitia; + sTrainingBonus += gGameExternalOptions.ubRpcBonusToTrainMilitia; + } + + // HEADROCK HAM 3.5: Training bonus given by local facilities + if (pTrainer->bSectorZ == 0) + { + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + if (gFacilityLocations[SECTOR(pTrainer->sSectorX, pTrainer->sSectorY)][cnt].fFacilityHere) + { + if (pTrainer->bAssignment == TRAIN_TOWN) + { + sTrainingBonus += (100 - gFacilityTypes[cnt].usMilitiaTraining); + } + else if (pTrainer->bAssignment == TRAIN_MOBILE) + { + sTrainingBonus += (100 - gFacilityTypes[cnt].usMobileMilitiaTraining); + } + } + } } // adjust for teaching bonus (a percentage) - sTotalTrainingPts += ( ( bTrainingBonus * sTotalTrainingPts ) / 100 ); + sTotalTrainingPts += ( ( sTrainingBonus * sTotalTrainingPts ) / 100 ); // teach bonus is considered "normal" - it's always there - *pusMaxPts += ( ( bTrainingBonus * *pusMaxPts ) / 100 ); + *pusMaxPts += ( ( sTrainingBonus * *pusMaxPts ) / 100 ); // adjust for fatigue of trainer @@ -4468,11 +4756,12 @@ void AssignmentDone( SOLDIERTYPE *pSoldier, BOOLEAN fSayQuote, BOOLEAN fMeToo ) if ( fSayQuote ) { - if ( ( fMeToo == FALSE ) && (pSoldier->bAssignment == TRAIN_TOWN ) ) + // HEADROCK HAM 3.6: Separated Militia Training + if ( ( fMeToo == FALSE ) && (pSoldier->bAssignment == TRAIN_TOWN || pSoldier->bAssignment == TRAIN_MOBILE ) ) { TacticalCharacterDialogue( pSoldier, QUOTE_ASSIGNMENT_COMPLETE ); - if( pSoldier->bAssignment == TRAIN_TOWN ) + if( pSoldier->bAssignment == TRAIN_TOWN || pSoldier->bAssignment == TRAIN_MOBILE ) { AddSectorForSoldierToListOfSectorsThatCompletedMilitiaTraining( pSoldier ); } @@ -4551,6 +4840,8 @@ void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) { UINT32 uiPercentHealth = 0; INT8 bActivityLevelDivisor = 0; + UINT16 usFacilityModifier = 100; + UINT8 ubAssignmentType = 0; // check if soldier valid @@ -4608,12 +4899,18 @@ void HandleHealingByNaturalCauses( SOLDIERTYPE *pSoldier ) bActivityLevelDivisor = gGameExternalOptions.ubMediumActivityLevel; } + // HEADROCK HAM 3.6: Add better healing rate from facility + ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + if (ubAssignmentType == FAC_PATIENT ) + { + usFacilityModifier = GetSectorModifier( pSoldier, FACILITY_PERFORMANCE_MOD ); + } // what percentage of health is he down to uiPercentHealth = ( pSoldier->stats.bLife * 100 ) / pSoldier->stats.bLifeMax; // gain that many hundredths of life points back, divided by the activity level modifier - pSoldier->sFractLife += ( INT16 ) ( uiPercentHealth / bActivityLevelDivisor ); + pSoldier->sFractLife += ( INT16 ) ((( uiPercentHealth / bActivityLevelDivisor ) * usFacilityModifier) / 100 ); // now update the real life values UpDateSoldierLife( pSoldier ); @@ -5866,6 +6163,16 @@ void HandleShadingOfLinesForAssignmentMenus( void ) // shade vehicle line ShadeStringInBox( ghAssignmentBox, ASSIGN_MENU_VEHICLE ); } + + if (BasicCanCharacterFacility( pSoldier )) + { + // unshade facility line + UnShadeStringInBox( ghAssignmentBox, ASSIGN_MENU_FACILITY ); + } + else + { + ShadeStringInBox( ghAssignmentBox, ASSIGN_MENU_FACILITY ); + } } } @@ -5884,6 +6191,12 @@ void HandleShadingOfLinesForAssignmentMenus( void ) // training attributes submenu HandleShadingOfLinesForAttributeMenus( ); + // HEADROCK HAM 3.6: Facility Menu + HandleShadingOfLinesForFacilityMenu( ); + + // HEADROCK HAM 3.6: Facility Submenu + HandleShadingOfLinesForFacilityAssignmentMenu( ); + return; } @@ -5932,6 +6245,8 @@ void DetermineWhichAssignmentMenusCanBeShown( void ) fShowAssignmentMenu = FALSE; fShowVehicleMenu = FALSE; fShowRepairMenu = FALSE; + // HEADROCK HAM 3.6: Reset Facility menu + fShowFacilityMenu = FALSE; // destroy mask, if needed CreateDestroyScreenMaskForAssignmentAndContractMenus( ); @@ -5944,6 +6259,9 @@ void DetermineWhichAssignmentMenusCanBeShown( void ) CreateDestroyMouseRegionsForAttributeMenu( ); CreateDestroyMouseRegionsForSquadMenu( TRUE ); CreateDestroyMouseRegionForRepairMenu( ); + // HEADROCK HAM 3.6: Facility Menu, Submenu + CreateDestroyMouseRegionForFacilityMenu( ); + CreateDestroyMouseRegionsForFacilityAssignmentMenu( ); // hide all boxes being shown if ( IsBoxShown( ghEpcBox ) ) @@ -5982,6 +6300,20 @@ void DetermineWhichAssignmentMenusCanBeShown( void ) fTeamPanelDirty = TRUE; gfRenderPBInterface = TRUE; } + // HEADROCK HAM 3.6: Facility Menu + if ( IsBoxShown( ghFacilityBox ) ) + { + HideBox( ghFacilityBox ); + fTeamPanelDirty = TRUE; + gfRenderPBInterface = TRUE; + } + // HEADROCK HAM 3.6: Facility Submenu + if ( IsBoxShown( ghFacilityAssignmentBox ) ) + { + HideBox( ghFacilityAssignmentBox ); + fTeamPanelDirty = TRUE; + gfRenderPBInterface = TRUE; + } // do we really want ot hide this box? if( fShowContractMenu == FALSE ) @@ -6110,9 +6442,48 @@ void DetermineWhichAssignmentMenusCanBeShown( void ) // SetRenderFlags(RENDER_FLAG_FULL); } } - CreateDestroyMouseRegionForVehicleMenu( ); + // HEADROCK HAM 3.6: FACILITY menu + if( fShowFacilityMenu == TRUE ) + { + HandleShadingOfLinesForFacilityMenu( ); + ShowBox( ghFacilityBox ); + } + else + { + if( IsBoxShown( ghFacilityBox ) ) + { + HideBox( ghFacilityBox ); + fTeamPanelDirty = TRUE; + fMapPanelDirty = TRUE; + gfRenderPBInterface = TRUE; + // SetRenderFlags(RENDER_FLAG_FULL); + } + } + CreateDestroyMouseRegionForFacilityMenu( ); + + // Facility Sub-menu + if( fShowFacilityAssignmentMenu == TRUE ) + { + HandleShadingOfLinesForFacilityAssignmentMenu( ); + ShowBox( ghFacilityAssignmentBox ); + } + else + { + if( IsBoxShown( ghFacilityAssignmentBox ) ) + { + HideBox( ghFacilityAssignmentBox ); + fTeamPanelDirty = TRUE; + fMapPanelDirty = TRUE; + gfRenderPBInterface = TRUE; + // SetRenderFlags(RENDER_FLAG_FULL); + } + + } + CreateDestroyMouseRegionsForFacilityAssignmentMenu( ); + + return; } @@ -6171,6 +6542,11 @@ void AssignmentScreenMaskBtnCallback(MOUSE_REGION * pRegion, INT32 iReason ) fShowContractMenu = FALSE; + // HEADROCK HAM 3.6: Facility Menu. + //fShowFacilityMenu = FALSE; + // And facility Submenu + //fShowFacilityAssignmentMenu = FALSE; + // stop showing town mine info fShowTownInfo = FALSE; @@ -6213,6 +6589,8 @@ void ClearScreenMaskForMapScreenExit( void ) CreateDestroyMouseRegionsForAttributeMenu( ); CreateDestroyMouseRegionsForSquadMenu( TRUE ); CreateDestroyMouseRegionForRepairMenu( ); + // HEADROCK HAM 3.6: Facility Menu + CreateDestroyMouseRegionForFacilityMenu( ); return; } @@ -6730,12 +7108,16 @@ void CreateDestroyMouseRegionsForRemoveMenu( void ) if( fShowRemoveMenu == FALSE ) { fShowAttributeMenu = FALSE; + // HEADROCK HAM 3.6: Stop showing Facility submenu + fShowFacilityAssignmentMenu = FALSE; fMapPanelDirty = TRUE; gfRenderPBInterface = TRUE; - } + + + RestorePopUpBoxes( ); fMapPanelDirty = TRUE; @@ -7499,9 +7881,6 @@ void TrainingMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) // btn callback handler for assignment region INT32 iValue = -1; SOLDIERTYPE * pSoldier = NULL; - INT8 bTownId; - CHAR16 sString[ 128 ]; - CHAR16 sStringA[ 128 ]; BOOLEAN fCanTrainMilitia = TRUE; @@ -7541,110 +7920,122 @@ void TrainingMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) DetermineBoxPositions( ); break; + // HEADROCK HAM 3.6: This is revamped for separating Mobile and Garrison training. case( TRAIN_MENU_TOWN): - if( BasicCanCharacterTrainMilitia(pSoldier) ) + + // Full test of Character and Sector to see if this training is possible at the moment. + if( !BasicCanCharacterTrainMilitia(pSoldier) ) { - bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); - - // if it's a town sector (the following 2 errors can't happen at non-town SAM sites) - if( bTownId != BLANK_SECTOR ) - { - // can we keep militia in this town? - if( MilitiaTrainingAllowedInSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) == FALSE ) - { - swprintf( sString, pMapErrorString[ 31 ], pTownNames[ bTownId ] ); - DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); - break; - } - - // is the current loyalty high enough to train some? - if( DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) == FALSE ) - { - DoScreenIndependantMessageBox( zMarksMapScreenText[ 20 ], MSG_BOX_FLAG_OK, NULL ); - break; - } - } - - - // Kaiden: Roaming Militia Training: - if(IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) - if (!gGameExternalOptions.gfmusttrainroaming) - fCanTrainMilitia = FALSE; - else if (GetWorldDay( ) < gGameExternalOptions.guiAllowMilitiaGroupsDelay) - fCanTrainMilitia = FALSE; - else if (IsThisSectorASAMSector(pSoldier->sSectorX,pSoldier->sSectorY,0 )) - fCanTrainMilitia = FALSE; - else - fCanTrainMilitia = TRUE; - - - if(!fCanTrainMilitia) - { - if( bTownId == BLANK_SECTOR ) - { - // SAM site - GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, sStringA ); - swprintf( sString, zMarksMapScreenText[ 21 ], sStringA ); - } - else - { - // town - swprintf( sString, zMarksMapScreenText[ 21 ], pTownNames[ bTownId ] ); - } - - DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); - break; - } - - if ( CountMilitiaTrainersInSoldiersSector( pSoldier ) >= gGameExternalOptions.ubMaxMilitiaTrainersPerSector ) - { - swprintf( sString, gzLateLocalizedString[ 47 ], gGameExternalOptions.ubMaxMilitiaTrainersPerSector ); - DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); - break; - } - - - // PASSED ALL THE TESTS - ALLOW SOLDIER TO TRAIN MILITIA HERE - - pSoldier->bOldAssignment = pSoldier->bAssignment; - - if( ( pSoldier->bAssignment != TRAIN_TOWN ) ) - { - SetTimeOfAssignmentChangeForMerc( pSoldier ); - } - - MakeSoldiersTacticalAnimationReflectAssignment( pSoldier ); - - // stop showing menu - fShowAssignmentMenu = FALSE; - giAssignHighLine = -1; - - // remove from squad - - if( pSoldier->bOldAssignment == VEHICLE ) - { - TakeSoldierOutOfVehicle( pSoldier ); - } - RemoveCharacterFromSquads( pSoldier ); - - ChangeSoldiersAssignment( pSoldier, TRAIN_TOWN ); - - // assign to a movement group - AssignMercToAMovementGroup( pSoldier ); - if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) - { - // show a message to confirm player wants to charge cost - HandleInterfaceMessageForCostOfTrainingMilitia( pSoldier ); - } - else - { - SetAssignmentForList( TRAIN_TOWN, 0 ); - } - - gfRenderPBInterface = TRUE; - + // No feedback. The menu options should be greyed out, anyway. + break; } - break; + + // Check for specific errors why this merc should not be able to train, + // and display a specific error message if one is encountered. + if( !CanCharacterTrainMilitiaWithErrorReport(pSoldier) ) + { + // Error found. Breaking. Note that the above function DOES display feedback if an error is + // encountered at all. + break; + } + + // PASSED BOTH TESTS - ALLOW SOLDIER TO TRAIN MILITIA HERE + + pSoldier->bOldAssignment = pSoldier->bAssignment; + + if( ( pSoldier->bAssignment != TRAIN_TOWN ) ) + { + SetTimeOfAssignmentChangeForMerc( pSoldier ); + } + + MakeSoldiersTacticalAnimationReflectAssignment( pSoldier ); + + // stop showing menu + fShowAssignmentMenu = FALSE; + giAssignHighLine = -1; + + // remove from squad + + if( pSoldier->bOldAssignment == VEHICLE ) + { + TakeSoldierOutOfVehicle( pSoldier ); + } + RemoveCharacterFromSquads( pSoldier ); + + ChangeSoldiersAssignment( pSoldier, TRAIN_TOWN ); + + // assign to a movement group + AssignMercToAMovementGroup( pSoldier ); + if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + { + // show a message to confirm player wants to charge cost + HandleInterfaceMessageForCostOfTrainingMilitia( pSoldier ); + } + else + { + SetAssignmentForList( TRAIN_TOWN, 0 ); + } + gfRenderPBInterface = TRUE; + break; + + // HEADROCK HAM 3.6: New separate Mobile Militia training. + case( TRAIN_MENU_MOBILE ): + + // Full test of Character and Sector to see if this training is possible at the moment. + if( !BasicCanCharacterTrainMobileMilitia(pSoldier) ) + { + // No feedback. The menu options should be greyed out, anyway. + break; + } + + // Check for specific errors why this merc should not be able to train, + // and display a specific error message if one is encountered. + if( !CanCharacterTrainMobileMilitiaWithErrorReport(pSoldier) ) + { + // Error found. Breaking. Note that the above function DOES display feedback if an error is + // encountered at all. + break; + } + + // PASSED BOTH TESTS - ALLOW SOLDIER TO TRAIN MILITIA HERE + + pSoldier->bOldAssignment = pSoldier->bAssignment; + + if( ( pSoldier->bAssignment != TRAIN_MOBILE ) ) + { + SetTimeOfAssignmentChangeForMerc( pSoldier ); + } + + MakeSoldiersTacticalAnimationReflectAssignment( pSoldier ); + + // stop showing menu + fShowAssignmentMenu = FALSE; + giAssignHighLine = -1; + + // remove from squad + + if( pSoldier->bOldAssignment == VEHICLE ) + { + TakeSoldierOutOfVehicle( pSoldier ); + } + RemoveCharacterFromSquads( pSoldier ); + + ChangeSoldiersAssignment( pSoldier, TRAIN_MOBILE ); + + // assign to a movement group + AssignMercToAMovementGroup( pSoldier ); + if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMobileMilitiaTrainingPaid == FALSE ) + { + // show a message to confirm player wants to charge cost + HandleInterfaceMessageForCostOfTrainingMilitia( pSoldier ); + } + else + { + SetAssignmentForList( TRAIN_MOBILE, 0 ); + } + gfRenderPBInterface = TRUE; + break; + case( TRAIN_MENU_TEAMMATES): if( CanCharacterTrainTeammates( pSoldier ) == TRUE ) @@ -7785,8 +8176,8 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP) { - - if( ( fShowAttributeMenu )||( fShowTrainingMenu ) || ( fShowRepairMenu ) || ( fShowVehicleMenu ) ||( fShowSquadMenu ) ) + // HEADROCK HAM 3.6: Added facility menu. + if( ( fShowAttributeMenu )||( fShowTrainingMenu ) || ( fShowRepairMenu ) || ( fShowVehicleMenu ) ||( fShowSquadMenu ) || ( fShowFacilityMenu ) || ( fShowFacilityAssignmentMenu ) ) { return; } @@ -7804,6 +8195,7 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) fShowSquadMenu = TRUE; fShowTrainingMenu = FALSE; fShowVehicleMenu = FALSE; + fShowFacilityMenu = FALSE; // HEADROCK HAM 3.6: Facility Menu fTeamPanelDirty = TRUE; fMapScreenBottomDirty = TRUE; @@ -7898,6 +8290,7 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) fShowSquadMenu = TRUE; fShowTrainingMenu = FALSE; fShowVehicleMenu = FALSE; + fShowFacilityMenu = FALSE; // HEADROCK HAM 3.6: Facility Menu fTeamPanelDirty = TRUE; fMapScreenBottomDirty = TRUE; fShowRepairMenu = FALSE; @@ -8021,6 +8414,7 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) fShowSquadMenu = FALSE; fShowTrainingMenu = FALSE; fShowVehicleMenu = FALSE; + fShowFacilityMenu = FALSE; // HEADROCK HAM 3.6: Facility Menu fTeamPanelDirty = TRUE; fMapScreenBottomDirty = TRUE; @@ -8056,11 +8450,36 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) fShowSquadMenu = FALSE; fShowVehicleMenu = FALSE; fShowRepairMenu = FALSE; + fShowFacilityMenu = FALSE; // HEADROCK HAM 3.6: Facility Menu fTeamPanelDirty = TRUE; fMapScreenBottomDirty = TRUE; } break; + // HEADROCK HAM 3.6: New assignments for Facility operation. + case( ASSIGN_MENU_FACILITY ): + if ( BasicCanCharacterFacility( pSoldier ) ) + { + // put character on a team + fShowSquadMenu = FALSE; + fShowTrainingMenu = FALSE; + fShowVehicleMenu = FALSE; + fShowRepairMenu = FALSE; + fShowFacilityMenu = TRUE; // HEADROCK HAM 3.6: Facility Menu + fTeamPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + + if( DisplayFacilityMenu( pSoldier ) ) + { + fShowFacilityMenu = TRUE; + ShowBox( ghFacilityBox ); + } + else + { + fShowFacilityMenu = FALSE; + } + } + break; case( ASSIGN_MENU_CANCEL ): fShowAssignmentMenu = FALSE; giAssignHighLine = -1; @@ -8079,13 +8498,16 @@ void AssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) } else if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) { - if( ( fShowAttributeMenu )||( fShowTrainingMenu ) || ( fShowRepairMenu ) || ( fShowVehicleMenu ) ||( fShowSquadMenu ) ) + // HEADROCK HAM 3.6: Added facility menu + if( ( fShowAttributeMenu )||( fShowTrainingMenu ) || ( fShowRepairMenu ) || ( fShowVehicleMenu ) ||( fShowSquadMenu ) || ( fShowFacilityMenu ) || ( fShowFacilityAssignmentMenu ) ) { fShowAttributeMenu = FALSE; fShowTrainingMenu = FALSE; fShowRepairMenu = FALSE; fShowVehicleMenu = FALSE; fShowSquadMenu = FALSE; + fShowFacilityMenu = FALSE; // Added facilities + fShowFacilityAssignmentMenu = FALSE; // Sub-menu for facilities // rerender tactical stuff gfRenderPBInterface = TRUE; @@ -8109,6 +8531,8 @@ void RestorePopUpBoxes( void ) AssignmentPosition.iX = OrigAssignmentPosition.iX ; TrainPosition.iX = OrigTrainPosition.iX; VehiclePosition.iX = OrigVehiclePosition.iX; + FacilityPosition.iX = OrigFacilityPosition.iX; + FacilityAssignmentPosition.iX = OrigFacilityAssignmentPosition.iX; return; } @@ -8819,6 +9243,9 @@ BOOLEAN CreateDestroyAssignmentPopUpBoxes( void ) CreateAttributeBox(); CreateVehicleBox(); CreateRepairBox( ); + // HEADROCK HAM 3.6: Facility Menu + CreateFacilityBox( ); + CreateFacilityAssignmentBox( ); UpdateMapScreenAssignmentPositions( ); @@ -8832,6 +9259,10 @@ BOOLEAN CreateDestroyAssignmentPopUpBoxes( void ) RemoveBox(ghAttributeBox); ghAttributeBox = -1; + // HEADROCK HAM 3.6: Remove Facility Assignment Menu + RemoveBox(ghFacilityAssignmentBox); + ghFacilityAssignmentBox = -1; + RemoveBox(ghVehicleBox); ghVehicleBox = -1; @@ -8847,6 +9278,10 @@ BOOLEAN CreateDestroyAssignmentPopUpBoxes( void ) RemoveBox(ghTrainingBox); ghTrainingBox = -1; + // HEADROCK HAM 3.6: Remove Facility Menu + RemoveBox(ghFacilityBox); + ghFacilityBox = -1; + fCreated = FALSE; gfIgnoreScrolling = FALSE; RebuildCurrentSquad( ); @@ -8938,6 +9373,40 @@ void DetermineBoxPositions( void ) } } + /*// HEADROCK HAM 3.6: Facility Menu + if( ( fShowFacilityMenu == TRUE ) && ( ghFacilityBox != -1 ) ) + { + CreateDestroyMouseRegionForFacilityMenu( ); + pNewPoint.iY += ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + + SetBoxPosition( ghFacilityBox, pNewPoint ); + }*/ + + // HEADROCK HAM 3.6: Facility Sub-menu + if( ( fShowFacilityMenu == TRUE ) && ( ghFacilityBox != -1 ) ) + { + //CreateDestroyMouseRegionForFacilityMenu( ); + pNewPoint.iY += ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + SetBoxPosition( ghFacilityBox, pNewPoint ); + FacilityPosition.iX = pNewPoint.iX; + FacilityPosition.iY = pNewPoint.iY; + OrigFacilityPosition.iY = pNewPoint.iY; + OrigFacilityPosition.iX = pNewPoint.iX; + + ResizeBoxToText( ghFacilityBox ); + + GetBoxSize( ghFacilityBox, &pDimensions ); + GetBoxPosition( ghFacilityBox, &pPoint ); + + if( ( fShowFacilityAssignmentMenu == TRUE ) && ( ghFacilityAssignmentBox != -1 ) ) + { + // hang it right beside the training box menu + pNewPoint.iX = pPoint.iX + pDimensions.iRight; + pNewPoint.iY = pPoint.iY; + SetBoxPosition( ghFacilityAssignmentBox, pNewPoint ); + } + } + return; } @@ -9161,6 +9630,73 @@ void CheckAndUpdateTacticalAssignmentPopUpPositions( void ) SetBoxPosition( ghTrainingBox, pPoint ); } + + // HEADROCK HAM 3.6: Facility Sub-menu + else if( fShowFacilityAssignmentMenu == TRUE ) + { + GetBoxSize( ghFacilityBox, &pDimensions ); + GetBoxSize( ghFacilityAssignmentBox, &pDimensions3 ); + + if( gsAssignmentBoxesX + pDimensions2.iRight + pDimensions.iRight + pDimensions3.iRight >= SCREEN_WIDTH ) + { + gsAssignmentBoxesX = ( INT16 ) ( (SCREEN_WIDTH - 1) - ( pDimensions2.iRight + pDimensions.iRight + pDimensions3.iRight ) ); + SetRenderFlags( RENDER_FLAG_FULL ); + } + + if( gsAssignmentBoxesY + pDimensions3.iBottom + ( GetFontHeight( MAP_SCREEN_FONT ) * ASSIGN_MENU_FACILITY ) >= (SCREEN_HEIGHT - 120) ) + { + gsAssignmentBoxesY = ( INT16 )( (SCREEN_HEIGHT - 121) - ( pDimensions3.iBottom ) ); + SetRenderFlags( RENDER_FLAG_FULL ); + } + + pPoint.iX = gsAssignmentBoxesX + pDimensions2.iRight + pDimensions.iRight; + pPoint.iY = gsAssignmentBoxesY; + + pPoint.iY += ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + SetBoxPosition( ghFacilityAssignmentBox, pPoint ); + + pPoint.iX = gsAssignmentBoxesX + pDimensions2.iRight; + pPoint.iY = gsAssignmentBoxesY; + + pPoint.iY += ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + + SetBoxPosition( ghFacilityBox, pPoint ); + + + } + + // HEADROCK HAM 3.6: Facility Menu + if( fShowFacilityMenu == TRUE ) + { + GetBoxSize( ghFacilityBox, &pDimensions ); + + if( gsAssignmentBoxesX + pDimensions2.iRight + pDimensions.iRight >= SCREEN_WIDTH ) + { + gsAssignmentBoxesX = ( INT16 ) ( (SCREEN_WIDTH - 1) - ( pDimensions2.iRight + pDimensions.iRight ) ); + SetRenderFlags( RENDER_FLAG_FULL ); + } + + if( pDimensions2.iBottom > pDimensions.iBottom ) + { + sLongest = ( INT16 )pDimensions2.iBottom + ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + } + else + { + sLongest = ( INT16 )pDimensions.iBottom + ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + } + + if( gsAssignmentBoxesY + sLongest >= (SCREEN_HEIGHT - 120) ) + { + gsAssignmentBoxesY = ( INT16 )( (SCREEN_HEIGHT - 121) - ( sLongest ) ); + SetRenderFlags( RENDER_FLAG_FULL ); + } + + pPoint.iX = gsAssignmentBoxesX + pDimensions2.iRight; + pPoint.iY = gsAssignmentBoxesY + ( ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY ); + + SetBoxPosition( ghFacilityBox, pPoint ); + } + else { // just the assignment box @@ -9258,6 +9794,24 @@ void HandleRestFatigueAndSleepStatus( void ) FatigueCharacter( pSoldier ); } + // HEADROCK HAM 3.5: Enforce breath limits from sector facilities + if (pSoldier->bSectorZ == 0) + { + // Find maximum breath allowed by facilities (lowest limit found will be used) + UINT8 ubMaxFatigue = (UINT8)GetSectorModifier( pSoldier, FACILITY_MAX_BREATH ); + if ( ubMaxFatigue > 0 && pSoldier->bBreathMax > ubMaxFatigue ) + { + // Normalize to the maximum allowed here. + pSoldier->bBreathMax = (pSoldier->bBreathMax + ubMaxFatigue) / 2; + + if (pSoldier->bBreath > pSoldier->bBreathMax) + { + // Adjust breath to max. + pSoldier->bBreath = pSoldier->bBreathMax; + } + } + } + // CHECK FOR MERCS GOING TO SLEEP @@ -9283,6 +9837,11 @@ void HandleRestFatigueAndSleepStatus( void ) TacticalCharacterDialogueWithSpecialEvent( pSoldier, QUOTE_NEED_SLEEP, DIALOGUE_SPECIAL_EVENT_SLEEP, 1,0 ); fMeToo = TRUE; } + // HEADROCK HAM 2.8/HAM 3.6: Run sleep synchronization between Trainers and Trainees + else + { + HandleTrainingSleepSynchronize( pSoldier ); + } // guy collapses pSoldier->flags.fMercCollapsedFlag = TRUE; @@ -9315,6 +9874,10 @@ void HandleRestFatigueAndSleepStatus( void ) // seems unnecessary now? ARM pSoldier->bOldAssignment = pSoldier->bAssignment; + + // HEADROCK HAM 2.8/HAM 3.6: If a trainer goes to sleep, we may need to put all his trainees to sleep as well. + // If all trainees are asleep, put the trainer to sleep as well. + HandleTrainingSleepSynchronize( pSoldier ); } } else // tired, in a squad / vehicle @@ -9384,7 +9947,8 @@ void HandleRestFatigueAndSleepStatus( void ) if ( pSoldier->flags.fMercAsleep ) { // but has had enough rest? - if( pSoldier->bBreathMax >= BREATHMAX_FULLY_RESTED ) + // HEADROCK HAM 3.5: Facilities can reduce maximum fatigue below 95... + if( pSoldier->bBreathMax >= __min(BREATHMAX_FULLY_RESTED, GetSectorModifier( pSoldier, FACILITY_MAX_BREATH ) ) ) { // try to wake merc up if( SetMercAwake( pSoldier, FALSE, FALSE ) ) @@ -9403,6 +9967,9 @@ void HandleRestFatigueAndSleepStatus( void ) AddSoldierToWaitingListQueue( pSoldier ); fBoxSetUp = TRUE; } + // HEADROCK HAM 2.8/HAM 3.6: If a trainer wakes up, we may need to wake up all his trainees as well. + // If all trainees are awake, wake up the trainer as well. + HandleTrainingWakeSynchronize( pSoldier ); } } } @@ -9759,6 +10326,49 @@ void SetSoldierAssignment( SOLDIERTYPE *pSoldier, INT8 bAssignment, INT32 iParam gfRenderPBInterface = TRUE; } break; + // HEADROCK HAM 3.6: Training mobiles is now a separate assignment. + case( TRAIN_MOBILE ): + if( CanCharacterTrainMobileMilitia( pSoldier ) ) + { + // train mobile militia + pSoldier->bOldAssignment = pSoldier->bAssignment; + + // set dirty flag + fTeamPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + + // remove from squad + RemoveCharacterFromSquads( pSoldier ); + + // remove from any vehicle + if( pSoldier->bOldAssignment == VEHICLE ) + { + TakeSoldierOutOfVehicle( pSoldier ); + } + + if( ( pSoldier->bAssignment != TRAIN_MOBILE ) ) + { + SetTimeOfAssignmentChangeForMerc( pSoldier ); + } + + ChangeSoldiersAssignment( pSoldier, TRAIN_MOBILE ); + + if( pMilitiaTrainerSoldier == NULL ) + { + if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + { + // show a message to confirm player wants to charge cost + HandleInterfaceMessageForCostOfTrainingMilitia( pSoldier ); + } + } + + AssignMercToAMovementGroup( pSoldier ); + // set dirty flag + fTeamPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + gfRenderPBInterface = TRUE; + } + break; case( TRAIN_SELF ): if( CanCharacterTrainStat( pSoldier, ( INT8 )iParam1, TRUE, FALSE ) ) { @@ -10080,6 +10690,13 @@ BOOLEAN HandleAssignmentExpansionAndHighLightForAssignMenu( SOLDIERTYPE *pSoldie HighLightBoxLine( ghAssignmentBox, ASSIGN_MENU_VEHICLE ); return( TRUE ); } + // HEADROCK HAM 3.6: Facility Menu. + else if( fShowFacilityMenu ) + { + // highlight the facility line in the previous menu + HighLightBoxLine( ghAssignmentBox, ASSIGN_MENU_FACILITY ); + return( TRUE ); + } return( FALSE ); } @@ -10204,15 +10821,41 @@ void HandleShadingOfLinesForTrainingMenu( void ) } else { + UnShadeStringInBox( ghTrainingBox, TRAIN_MENU_TOWN ); SecondaryShadeStringInBox( ghTrainingBox, TRAIN_MENU_TOWN ); } } else { + UnSecondaryShadeStringInBox( ghTrainingBox, TRAIN_MENU_TOWN ); // shade train militia line ShadeStringInBox( ghTrainingBox, TRAIN_MENU_TOWN ); } + // HEADROCK HAM 3.6: Training Mobile Militia. + // can character EVER train Mobile Militia? + if( BasicCanCharacterTrainMobileMilitia( pSoldier ) ) + { + // can he train here, now? + if( CanCharacterTrainMobileMilitia( pSoldier ) ) + { + // unshade train militia line + UnShadeStringInBox( ghTrainingBox, TRAIN_MENU_MOBILE ); + UnSecondaryShadeStringInBox( ghTrainingBox, TRAIN_MENU_MOBILE ); + } + else + { + UnShadeStringInBox( ghTrainingBox, TRAIN_MENU_MOBILE ); + SecondaryShadeStringInBox( ghTrainingBox, TRAIN_MENU_MOBILE ); + } + } + else + { + UnSecondaryShadeStringInBox( ghTrainingBox, TRAIN_MENU_MOBILE ); + // shade train militia line + ShadeStringInBox( ghTrainingBox, TRAIN_MENU_MOBILE ); + } + // can character train teammates? if( CanCharacterTrainTeammates( pSoldier ) == FALSE ) @@ -10297,7 +10940,7 @@ void HandleShadingOfLinesForAttributeMenus( void ) } -void ResetAssignmentsForAllSoldiersInSectorWhoAreTrainingTown( SOLDIERTYPE *pSoldier ) +/*void ResetAssignmentsForAllSoldiersInSectorWhoAreTrainingTown( SOLDIERTYPE *pSoldier ) { INT32 iNumberOnTeam = 0, iCounter = 0; SOLDIERTYPE *pCurSoldier = NULL; @@ -10321,7 +10964,7 @@ void ResetAssignmentsForAllSoldiersInSectorWhoAreTrainingTown( SOLDIERTYPE *pSol } return; -} +}*/ void ReportTrainersTraineesWithoutPartners( void ) @@ -10446,6 +11089,10 @@ BOOLEAN PutMercInAwakeState( SOLDIERTYPE *pSoldier ) // set merc awake pSoldier->flags.fMercAsleep = FALSE; + // HEADROCK HAM 3.6: Merc hasn't been working all this time, so let's reset his assignment start time. This + // will squash an exploit, and is a bit more realistic anyway. + pSoldier->uiLastAssignmentChangeMin = GetWorldTotalMin(); + // refresh panels fCharacterInfoPanelDirty = TRUE; fTeamPanelDirty = TRUE; @@ -10887,7 +11534,12 @@ void ReEvaluateEveryonesNothingToDo() break; case PATIENT: - fNothingToDo = !CanCharacterPatient( pSoldier ) || ( AnyDoctorWhoCanHealThisPatient( pSoldier, HEALABLE_EVER ) == NULL ); + // HEADROCK HAM 3.6: Characters can be facility patients, in which case they are resting with a + // bonus or penalty to their healing rate. No doctor is actually required, even though the + // character is still eligible for a doctor's treatment. + fNothingToDo = !CanCharacterPatient( pSoldier ) || + ( AnyDoctorWhoCanHealThisPatient( pSoldier, HEALABLE_EVER ) == NULL && + GetSoldierFacilityAssignmentIndex( pSoldier ) != FAC_PATIENT ); break; case ASSIGNMENT_HOSPITAL: @@ -10902,6 +11554,11 @@ void ReEvaluateEveryonesNothingToDo() fNothingToDo = !CanCharacterTrainMilitia( pSoldier ); break; + // HEADROCK HAM 3.6: Mobile Militia Training + case TRAIN_MOBILE: + fNothingToDo = !CanCharacterTrainMobileMilitia( pSoldier ); + break; + case TRAIN_SELF: fNothingToDo = !CanCharacterTrainStat( pSoldier, pSoldier->bTrainStat, TRUE, FALSE ); break; @@ -10921,7 +11578,12 @@ void ReEvaluateEveryonesNothingToDo() fNothingToDo = FALSE; break; } - + // HEADROCK HAM 3.6: Separate "can do" checks when the character is using a facility. + if (fNothingToDo == FALSE && GetSoldierFacilityAssignmentIndex(pSoldier) >= 0) + { + // Can this soldier continue working at the same facility doing the same job? + fNothingToDo = !CanCharacterFacility( pSoldier, (UINT8)pSoldier->sFacilityTypeOperated, (UINT8)GetSoldierFacilityAssignmentIndex(pSoldier) ); + } // if his flag is wrong if ( fNothingToDo != pSoldier->flags.fDoneAssignmentAndNothingToDoFlag ) @@ -11076,6 +11738,15 @@ void SetAssignmentForList( INT8 bAssignment, INT8 bParam ) fItWorked = TRUE; } break; + // HEADROCK HAM 3.6: Mobile Militia Training. + case( TRAIN_MOBILE ): + if( CanCharacterTrainMobileMilitia( pSoldier ) ) + { + pSoldier->bOldAssignment = pSoldier->bAssignment; + SetSoldierAssignment( pSoldier, TRAIN_MOBILE, 0, 0, 0 ); + fItWorked = TRUE; + } + break; case( TRAIN_TEAMMATE ): if( CanCharacterTrainStat( pSoldier, bParam, FALSE, TRUE ) ) { @@ -11092,6 +11763,17 @@ void SetAssignmentForList( INT8 bAssignment, INT8 bParam ) fItWorked = TRUE; } break; + + // HEADROCK HAM 3.6: Facility Staffing + case FACILITY_STAFF: + if ( CanCharacterFacility( pSoldier, bParam, FAC_STAFF ) ) + { + pSoldier->bOldAssignment = pSoldier->bAssignment; + ChangeSoldiersAssignment( pSoldier, FACILITY_STAFF ); + pSoldier->sFacilityTypeOperated = bParam; + fItWorked = TRUE; + } + break; case( SQUAD_1 ): case( SQUAD_2 ): @@ -11251,7 +11933,6 @@ BOOLEAN ValidTrainingPartnerInSameSectorOnAssignmentFound( SOLDIERTYPE *pTargetS INT32 iCounter = 0; SOLDIERTYPE *pSoldier = NULL; INT16 sTrainingPts = 0; - BOOLEAN fAtGunRange = FALSE; UINT16 usMaxPts; @@ -11279,21 +11960,15 @@ BOOLEAN ValidTrainingPartnerInSameSectorOnAssignmentFound( SOLDIERTYPE *pTargetS { // so far so good, now let's see if the trainer can really teach the student anything new - // are we training in the sector with gun range in Alma? - if ( ( pSoldier->sSectorX == GUN_RANGE_X ) && (pSoldier->sSectorY == GUN_RANGE_Y ) && ( pSoldier->bSectorZ == GUN_RANGE_Z ) ) - { - fAtGunRange = TRUE; - } - if ( pSoldier->bAssignment == TRAIN_TEAMMATE ) { // pSoldier is the instructor, target is the student - sTrainingPts = GetBonusTrainingPtsDueToInstructor( pSoldier, pTargetSoldier, bTargetStat, fAtGunRange, &usMaxPts ); + sTrainingPts = GetBonusTrainingPtsDueToInstructor( pSoldier, pTargetSoldier, bTargetStat, &usMaxPts ); } else { // target is the instructor, pSoldier is the student - sTrainingPts = GetBonusTrainingPtsDueToInstructor( pTargetSoldier, pSoldier, bTargetStat, fAtGunRange, &usMaxPts ); + sTrainingPts = GetBonusTrainingPtsDueToInstructor( pTargetSoldier, pSoldier, bTargetStat, &usMaxPts ); } if ( sTrainingPts > 0 ) @@ -11407,6 +12082,7 @@ BOOLEAN CharacterIsTakingItEasy( SOLDIERTYPE *pSoldier ) { // on duty, but able to catch naps (either not traveling, or not the driver of the vehicle) // The actual checks for this are in the "can he sleep" check above + if ( ( pSoldier->bAssignment < ON_DUTY ) || ( pSoldier->bAssignment == VEHICLE ) ) { return( TRUE ); @@ -11418,6 +12094,30 @@ BOOLEAN CharacterIsTakingItEasy( SOLDIERTYPE *pSoldier ) return( TRUE ); } + // HEADROCK HAM 2.8/HAM 3.6: Trainers whose trainees are all asleep will not become fatigued. + if ( pSoldier->bAssignment == TRAIN_TEAMMATE && (gGameExternalOptions.ubSmartTrainingRest == 1 || gGameExternalOptions.ubSmartTrainingRest == 2)) + { + if (FindAnyAwakeTrainees( pSoldier ) == FALSE) + { + return( TRUE ); + } + } + + // HEADROCK HAM 2.8/HAM 3.6: Characters in training whose trainer is asleep will not be fatigued. + if ( pSoldier->bAssignment == TRAIN_BY_OTHER && (gGameExternalOptions.ubSmartTrainingRest == 1 || gGameExternalOptions.ubSmartTrainingRest == 3)) + { + if (FindAnyAwakeTrainers( pSoldier ) == FALSE) + { + return( TRUE ); + } + } + + // HEADROCK HAM 3.6: Added new resting assignment for facilities only. + if ( pSoldier->bAssignment == FACILITY_REST ) + { + return( TRUE ); + } + // on a real assignment, but done with it? if ( pSoldier->flags.fDoneAssignmentAndNothingToDoFlag ) { @@ -11441,37 +12141,51 @@ UINT8 CalcSoldierNeedForSleep( SOLDIERTYPE *pSoldier ) // base comes from profile ubNeedForSleep = gMercProfiles[ pSoldier->ubProfile ].ubNeedForSleep; + // Enforce a maximum of 12 hours before injury penalties. + if ( ubNeedForSleep > 12 ) + { + ubNeedForSleep = 12; + } + + // Enforce a minimum of 6 hours, before night/day considerations. + if ( ubNeedForSleep < 6 ) + { + ubNeedForSleep = 6; + } + + // HEADROCK HAM 3.5: WTF! This calculation is NOT correct! + //ubPercentHealth = pSoldier->stats.bLife / pSoldier->stats.bLifeMax; ubPercentHealth = (pSoldier->stats.bLife*100) / pSoldier->stats.bLifeMax; + // Increase need for sleep based on injuries. if ( ubPercentHealth < 75 ) { ubNeedForSleep++; if ( ubPercentHealth < 50 ) { - ubNeedForSleep++; + ubNeedForSleep += 2; // 3 extra hours a day if ( ubPercentHealth < 25 ) { - ubNeedForSleep += 2; + ubNeedForSleep += 4; // 7 extra hours a day } } } - // reduce for each Night Ops or Martial Arts trait - ubNeedForSleep -= NUM_SKILL_TRAITS( pSoldier, NIGHTOPS ); + // Re-Enforce a maximum of 18 hours after injury penalties. + if ( ubNeedForSleep > 18 ) + { + ubNeedForSleep = 18; + } + + // reduce for Night Ops trait + // HEADROCK HAM 3.6: This is now split and applied depending on whether the merc is resting or working. + //ubNeedForSleep -= NUM_SKILL_TRAITS( pSoldier, NIGHTOPS ); + + // reduce for Martial Arts trait ubNeedForSleep -= NUM_SKILL_TRAITS( pSoldier, MARTIALARTS ); - if ( ubNeedForSleep < 4 ) - { - ubNeedForSleep = 4; - } - - if ( ubNeedForSleep > 12 ) - { - ubNeedForSleep = 12; - } - return( ubNeedForSleep ); } @@ -11772,7 +12486,7 @@ BOOLEAN SetTrainerSleepWhenTraineesSleep( SOLDIERTYPE *pThisTrainee) { UINT16 sMapX = pThisTrainee->sSectorX; UINT16 sMapY = pThisTrainee->sSectorY; - UINT16 sMapZ = pThisTrainee->sZLevel; + UINT16 sMapZ = pThisTrainee->bSectorZ; UINT8 bStat = pThisTrainee->bTrainStat; INT32 iCounter, iNumberOnTeam; @@ -11794,7 +12508,7 @@ BOOLEAN SetTrainerSleepWhenTraineesSleep( SOLDIERTYPE *pThisTrainee) { pOtherTrainee = &Menptr[ iCounter ]; if (pOtherTrainee->bAssignment == TRAIN_BY_OTHER && pOtherTrainee->bTrainStat == pThisTrainee->bTrainStat && - pOtherTrainee->sSectorX == sMapX && pOtherTrainee->sSectorY == sMapY && pOtherTrainee->sZLevel == sMapZ && + pOtherTrainee->sSectorX == sMapX && pOtherTrainee->sSectorY == sMapY && pOtherTrainee->bSectorZ == sMapZ && pOtherTrainee->bActive && !pOtherTrainee->flags.fMercAsleep ) { // Trainee is present and awake. Flag is reset to false. @@ -11809,7 +12523,7 @@ BOOLEAN SetTrainerSleepWhenTraineesSleep( SOLDIERTYPE *pThisTrainee) { pTrainer = &Menptr[ iCounter ]; if (pTrainer->bAssignment == TRAIN_TEAMMATE && pTrainer->bTrainStat == pThisTrainee->bTrainStat && - pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->sZLevel == sMapZ && + pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->bSectorZ == sMapZ && pTrainer->bActive && !pTrainer->flags.fMercAsleep ) { // Trainer will go to sleep @@ -11848,7 +12562,7 @@ BOOLEAN SetTraineesSleepWhenTrainerSleeps( SOLDIERTYPE *pTrainer) { UINT16 sMapX = pTrainer->sSectorX; UINT16 sMapY = pTrainer->sSectorY; - UINT16 sMapZ = pTrainer->sZLevel; + UINT16 sMapZ = pTrainer->bSectorZ; UINT8 bStat = pTrainer->bTrainStat; INT32 iCounter, iNumberOnTeam; BOOLEAN fTraineesSentToSleep = FALSE; @@ -11867,7 +12581,7 @@ BOOLEAN SetTraineesSleepWhenTrainerSleeps( SOLDIERTYPE *pTrainer) { pTrainee = &Menptr[ iCounter ]; if (pTrainee->bAssignment == TRAIN_BY_OTHER && pTrainee->bTrainStat == pTrainer->bTrainStat && - pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->sZLevel == sMapZ && + pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->bSectorZ == sMapZ && pTrainee->bActive && !pTrainee->flags.fMercAsleep ) { // Trainee will go to sleep @@ -11902,7 +12616,7 @@ BOOLEAN SetTrainerWakeWhenTraineesWake( SOLDIERTYPE *pThisTrainee) { UINT16 sMapX = pThisTrainee->sSectorX; UINT16 sMapY = pThisTrainee->sSectorY; - UINT16 sMapZ = pThisTrainee->sZLevel; + UINT16 sMapZ = pThisTrainee->bSectorZ; UINT8 bStat = pThisTrainee->bTrainStat; INT32 iCounter, iNumberOnTeam; @@ -11924,7 +12638,7 @@ BOOLEAN SetTrainerWakeWhenTraineesWake( SOLDIERTYPE *pThisTrainee) { pOtherTrainee = &Menptr[ iCounter ]; if (pOtherTrainee->bAssignment == TRAIN_BY_OTHER && pOtherTrainee->bTrainStat == pThisTrainee->bTrainStat && - pOtherTrainee->sSectorX == sMapX && pOtherTrainee->sSectorY == sMapY && pOtherTrainee->sZLevel == sMapZ && + pOtherTrainee->sSectorX == sMapX && pOtherTrainee->sSectorY == sMapY && pOtherTrainee->bSectorZ == sMapZ && pOtherTrainee->bActive && pOtherTrainee->flags.fMercAsleep ) { // Trainee is present and asleep. Flag is reset to FALSE. @@ -11939,7 +12653,7 @@ BOOLEAN SetTrainerWakeWhenTraineesWake( SOLDIERTYPE *pThisTrainee) { pTrainer = &Menptr[ iCounter ]; if (pTrainer->bAssignment == TRAIN_TEAMMATE && pTrainer->bTrainStat == pThisTrainee->bTrainStat && - pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->sZLevel == sMapZ && + pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->bSectorZ == sMapZ && pTrainer->bActive && pTrainer->flags.fMercAsleep ) { // Trainer will wake up @@ -11977,7 +12691,7 @@ BOOLEAN SetTraineesWakeWhenTrainerWakes( SOLDIERTYPE *pTrainer) { UINT16 sMapX = pTrainer->sSectorX; UINT16 sMapY = pTrainer->sSectorY; - UINT16 sMapZ = pTrainer->sZLevel; + UINT16 sMapZ = pTrainer->bSectorZ; UINT8 bStat = pTrainer->bTrainStat; INT32 iCounter, iNumberOnTeam; BOOLEAN fTraineesWokenUp = FALSE; @@ -11996,7 +12710,7 @@ BOOLEAN SetTraineesWakeWhenTrainerWakes( SOLDIERTYPE *pTrainer) { pTrainee = &Menptr[ iCounter ]; if (pTrainee->bAssignment == TRAIN_BY_OTHER && pTrainee->bTrainStat == pTrainer->bTrainStat && - pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->sZLevel == sMapZ && + pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->bSectorZ == sMapZ && pTrainee->bActive && pTrainee->flags.fMercAsleep ) { // Trainee will wake up @@ -12060,9 +12774,9 @@ BOOLEAN FindAnyAwakeTrainers( SOLDIERTYPE *pTrainee ) { UINT16 sMapX = pTrainee->sSectorX; UINT16 sMapY = pTrainee->sSectorY; - UINT16 sMapZ = pTrainee->sZLevel; + UINT16 sMapZ = pTrainee->bSectorZ; UINT8 bStat = pTrainee->bTrainStat; - INT32 iCounter, iNumberOnTeam; + INT32 ubCounter = 0; BOOLEAN fAllTrainersAsleep = TRUE; SOLDIERTYPE * pTrainer; @@ -12073,17 +12787,19 @@ BOOLEAN FindAnyAwakeTrainers( SOLDIERTYPE *pTrainee ) return(FALSE); } - iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; - - for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + while(gCharactersList[ubCounter].fValid) { - pTrainer = &Menptr[ iCounter ]; + pTrainer = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is trainer awake? if (pTrainer->bAssignment == TRAIN_TEAMMATE && pTrainer->bTrainStat == pTrainee->bTrainStat && - pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->sZLevel == sMapZ && + pTrainer->sSectorX == sMapX && pTrainer->sSectorY == sMapY && pTrainer->bSectorZ == sMapZ && pTrainer->bActive && !pTrainer->flags.fMercAsleep ) { + // Reset flag fAllTrainersAsleep = FALSE; } + ubCounter++; } return(fAllTrainersAsleep); @@ -12093,9 +12809,9 @@ BOOLEAN FindAnyAwakeTrainees( SOLDIERTYPE *pTrainer ) { UINT16 sMapX = pTrainer->sSectorX; UINT16 sMapY = pTrainer->sSectorY; - UINT16 sMapZ = pTrainer->sZLevel; + UINT16 sMapZ = pTrainer->bSectorZ; UINT8 bStat = pTrainer->bTrainStat; - INT32 iCounter, iNumberOnTeam; + INT32 ubCounter = 0; BOOLEAN fAllTraineesAsleep = TRUE; SOLDIERTYPE * pTrainee; @@ -12106,18 +12822,2539 @@ BOOLEAN FindAnyAwakeTrainees( SOLDIERTYPE *pTrainer ) return(FALSE); } - iNumberOnTeam =gTacticalStatus.Team[ OUR_TEAM ].bLastID; - - for( iCounter = 0; iCounter < iNumberOnTeam; iCounter++ ) + while(gCharactersList[ubCounter].fValid) { - pTrainee = &Menptr[ iCounter ]; + pTrainee = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is trainee awake? if (pTrainee->bAssignment == TRAIN_BY_OTHER && pTrainee->bTrainStat == pTrainer->bTrainStat && - pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->sZLevel == sMapZ && + pTrainee->sSectorX == sMapX && pTrainee->sSectorY == sMapY && pTrainee->bSectorZ == sMapZ && pTrainee->bActive && !pTrainee->flags.fMercAsleep ) { + // Reset flag. fAllTraineesAsleep = FALSE; } + ubCounter++; } return(fAllTraineesAsleep); } + +// HEADROCK HAM 3.6: A new set of functions (this & next) to determine whether a character can train Mobile Militia. +// This function tests character statistics. + +BOOLEAN BasicCanCharacterTrainMobileMilitia( SOLDIERTYPE *pSoldier ) +{ + + ///////////////////////////////////////////////////// + // Tests whether character can do assignments at all! + + AssertNotNIL(pSoldier); + + if ( !BasicCanCharacterAssignment( pSoldier, TRUE ) ) + { + return( FALSE ); + } + + // Mobile Training allowed by INI settings? + if (!gGameExternalOptions.gfmusttrainroaming || // Mobiles turned off? + GetWorldDay( ) < gGameExternalOptions.guiAllowMilitiaGroupsDelay) // Mobiles not yet available? + { + // No Mobile Militia training allowed! + return ( FALSE ); + } + + // Is character dead or unconscious? + if( pSoldier->stats.bLife < OKLIFE ) + { + // dead or unconscious... + return ( FALSE ); + } + + // Is character underground? + if( pSoldier->bSectorZ != 0 ) + { + // underground training is not allowed (code doesn't support and it's a reasonable enough limitation) + return( FALSE ); + } + + // Is character on the way into/out of Arulco? + if( IsCharacterInTransit( pSoldier ) == TRUE ) + { + return ( FALSE ); + } + + // Is character travelling between sectors? + if( CharacterIsBetweenSectors( pSoldier ) ) + { + return( FALSE ); + } + + // Is character an Escortee? + if( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__EPC ) + { + // epcs can't do this + return( FALSE ); + } + + // Is character a Vehicle or Robot? + if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || AM_A_ROBOT( pSoldier ) ) + { + return( FALSE ); + } + + // IS character inside a helicopter over a hostile sector? + if( pSoldier->bAssignment == VEHICLE ) + { + if( ( iHelicopterVehicleId != -1 ) && ( pSoldier->iVehicleId == iHelicopterVehicleId ) ) + { + // enemies in sector + if( NumEnemiesInSector( pSoldier->sSectorX, pSoldier->sSectorY ) > 0 ) + { + return( FALSE ); + } + } + } + + //////////////////////////////////////////////////////////////////////// + // Tests to see whether this sector allows training militia for ANYBODY. + + // is there a city in the character's current sector? + INT8 bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + if (!bTownId) + { + // No city? No Mobiles. + return ( FALSE ); + } + else + { + // There's a city here. Does it allow training militia? + if (!MilitiaTrainingAllowedInTown( bTownId )) + { + // City does not allow militia training at all. + return ( FALSE ); + } + } + + // HEADROCK HAM 3.5: Only facilities allow militia training, and determine how many trainers can work here. + // Does sector have at least one facility that allows training? + // HEADROCK HAM 3.5: Only facilities allow militia training, and determine how many trainers can work here. + // Does sector have at least one facility that allows training? + BOOLEAN fMobileMilitiaTrainingAllowed = FALSE; + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + // Is this facility here? + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + // Does it allow training militia? + if (gFacilityTypes[cnt].ubMobileMilitiaTrainersAllowed) + { + // Cool. + fMobileMilitiaTrainingAllowed = TRUE; + } + } + } + if (!fMobileMilitiaTrainingAllowed) + { + // Mobile Militia training NOT allowed here! + return ( FALSE ); + } + + return ( TRUE ); +} + +// HEADROCK HAM 3.6: A new set of functions (this & previous) to determine whether a character can train Mobile Militia. +// This function tests location. + +BOOLEAN CanCharacterTrainMobileMilitia( SOLDIERTYPE *pSoldier ) +{ + AssertNotNIL(pSoldier); + + // Make sure the basic sector/merc variables are still applicable. This is simply a fail-safe. + if( !BasicCanCharacterTrainMobileMilitia( pSoldier ) ) + { + // Soldier/Sector have somehow failed the basic test. Character automatically fails this test as well. + return( FALSE ); + } + + if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + return( FALSE ); + } + + // Has leadership skill? + if( pSoldier->stats.bLeadership <= 0 ) + { + // no skill whatsoever + return ( FALSE ); + } + + // Sector Loyalty above minimum? + if( !DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) ) + { + // Not enough Loyalty... + return ( FALSE ); + } + + // HEADROCK HAM 3: When "Minimum Leadership for Militia Training" is enforced, this value holds the + // merc's effective leadership, after the "TEACHER" trait is taken into account. + UINT16 usEffectiveLeadership; + + // HEADROCK HAM 3: Determine whether the merc has enough leadership to train militia. The teacher trait may + // increase or decrease the effective skill. + if( gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia > 0 ) + { + // Read BASE leadership + usEffectiveLeadership = pSoldier->stats.bLeadership; + + // Apply modifier for TEACHER trait, if that feature is activated + if ( gGameExternalOptions.usTeacherTraitEffectOnLeadership > 0 && gGameExternalOptions.usTeacherTraitEffectOnLeadership != 100 ) + { + // Modifier applied once for each TEACHING level. + for (UINT8 i = 0; i < NUM_SKILL_TRAITS( pSoldier, TEACHING ); i++ ) + { + // This is a percentage modifier. + usEffectiveLeadership = (usEffectiveLeadership * gGameExternalOptions.usTeacherTraitEffectOnLeadership)/100; + } + } + + usEffectiveLeadership = __min(100,usEffectiveLeadership); + + // Is leadership too low to proceed? + if (usEffectiveLeadership < gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia) + { + return ( FALSE ); + } + } + + //////////////////////////////////////////////// + // Check whether controlled town sectors already have full militia + + INT32 iCounter = 0; + INT8 bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR(pSoldier->sSectorX, pSoldier->sSectorY) ] ); + BOOLEAN fUnfullSectorFound = FALSE; + + if ( CountMilitia( pSectorInfo ) < gGameExternalOptions.iMaxMilitiaPerSector ) + { + fUnfullSectorFound = TRUE; + } + else + { + iCounter = 0; + while( pTownNamesList[ iCounter ] != 0 ) + { + // Are we in this city? + if( pTownNamesList[ iCounter] == bTownId ) + { + INT16 sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + INT16 sCurrentY = GET_Y_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + + pSectorInfo = &( SectorInfo[ SECTOR(sCurrentX, sCurrentY) ] ); + // 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. This sector cannot generate militia anyway. + iCounter++; + continue; + } + else + { + if (CountMilitia(pSectorInfo) < gGameExternalOptions.iMaxMilitiaPerSector ) + { + // Found a controlled city sector that does not yet have a full garrison + fUnfullSectorFound = TRUE; + } + } + } + iCounter++; + } + } + if (fUnfullSectorFound) + { + // At least one city sector is controlled but not full of garrison militia. Can't train mobiles! + return (FALSE); + } + + ////////////////////////////////////////////// + // HEADROCK HAM 3.5: Militia Training Facility + // + + // Militia training is enabled in the sector only if there is a facility that allows this here. + // If one or more facilities are found, positive values are summed up and presented as the number + // of trainers allowed in the sector. Values are read from XML, and can be set to mimic JA2 + // defaults. This renders the INI setting "MAX_MILITIA_TRAINERS.." obsolete. + + // HEADROCK HAM 3.5: Only facilities allow militia training, and determine how many trainers can work here. + // Does sector have at least one facility that allows training? + UINT8 ubFacilityTrainersAllowed = 0; + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + // Is this facility here? + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + // Increase tally + ubFacilityTrainersAllowed += gFacilityTypes[cnt].ubMobileMilitiaTrainersAllowed; + } + } + + // Count number of trainers already operating here + if ( CountMilitiaTrainersInSoldiersSector( pSoldier, MOBILE_MILITIA ) >= ubFacilityTrainersAllowed ) + { + // Too many trainers in sector. + return (FALSE); + } + + // This will be replaced with an appropriate check to see if we have militia presence in this city at all. + // For now, Mobile Militia training is allowed regardless of how many troopers you have here. + // + // Is town full of Elites? + //if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) + //{ + // // Town is full of Elites. No further training required. + // // Also note that this takes care of Regulars as well, if Elite training is disabled. + // return( FALSE ); + //} + + // If we've reached this, then all is well. + return( TRUE ); + +} + +BOOLEAN CanCharacterTrainMilitiaWithErrorReport( SOLDIERTYPE *pSoldier ) +{ + // Temp string. + CHAR16 sString[ 128 ]; + CHAR16 sStringA[ 128 ]; + + // Enemies present? + if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + // Report "Enemies present!" + DoScreenIndependantMessageBox( New113HAMMessage[5], MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + + /////////////////////////////// + // Test for required Leadership + + UINT16 usEffectiveLeadership = pSoldier->stats.bLeadership; // Basic leadership score + BOOLEAN fSufficientLeadership = TRUE; // Result of check + + // Apply modifier for TEACHER trait, if that feature is activated + if ( gGameExternalOptions.usTeacherTraitEffectOnLeadership > 0 && gGameExternalOptions.usTeacherTraitEffectOnLeadership != 100 ) + { + // Modifier applied once for each TEACHING level. + for (UINT8 i = 0; i < NUM_SKILL_TRAITS( pSoldier, TEACHING ); i++ ) + { + // This is a percentage modifier. + usEffectiveLeadership = (usEffectiveLeadership * gGameExternalOptions.usTeacherTraitEffectOnLeadership)/100; + } + usEffectiveLeadership = __min(100,usEffectiveLeadership); + } + + // Is there an INI-set requirement? + if (gGameExternalOptions.ubMinimumLeadershipToTrainMilitia) + { + // Does he fail the requirement? + if (usEffectiveLeadership < gGameExternalOptions.ubMinimumLeadershipToTrainMilitia) + { + fSufficientLeadership = FALSE; + } + } + // If there is no requirement, does the soldier have ANY leadership skill? + else if (usEffectiveLeadership <= 0) + { + fSufficientLeadership = FALSE; + } + + // Failed above leadership tests? + if( !fSufficientLeadership ) + { + // Report "Insufficient Leadership Skill" + swprintf(sString, New113HAMMessage[6], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return ( FALSE ); + } + + //////////////////////// + // Test for town loyalty + + INT8 bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + + // Is this a town sector? + if( bTownId != BLANK_SECTOR ) + { + // is the current loyalty high enough to train militia at all? + if( DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) == FALSE ) + { + // Report "Not enough loyalty!" + DoScreenIndependantMessageBox( zMarksMapScreenText[ 20 ], MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + } + + // HEADROCK HAM 3.6: To be moved into the Mobile Militia training routine.... + // Kaiden: Roaming Militia Training: + //if(IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) + // if (!gGameExternalOptions.gfmusttrainroaming) + // fCanTrainMilitia = FALSE; + // else if (GetWorldDay( ) < gGameExternalOptions.guiAllowMilitiaGroupsDelay) + // fCanTrainMilitia = FALSE; + // else if (IsThisSectorASAMSector(pSoldier->sSectorX,pSoldier->sSectorY,0 )) + // fCanTrainMilitia = FALSE; + // else + // fCanTrainMilitia = TRUE; + + //////////////////////////////// + // Test for Militia Capacity + + if(IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) + { + if( bTownId == BLANK_SECTOR ) + { + // SAM site + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, sStringA ); + swprintf( sString, zMarksMapScreenText[ 21 ], sStringA ); + } + else + { + // town + swprintf( sString, zMarksMapScreenText[ 21 ], pTownNames[ bTownId ] ); + } + + // Report "Not enough room for Militia!" + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + ////////////////////////////////////////////// + // HEADROCK HAM 3.5: Militia Training Facility + // + // Militia training is enabled in the sector only if there is a facility that allows this here. + // If one or more facilities are found, positive values are summed up and presented as the number + // of trainers allowed in the sector. Values are read from XML, and can be set to mimic JA2 + // defaults. This renders the INI setting "MAX_MILITIA_TRAINERS.." obsolete. + + UINT8 ubFacilityTrainersAllowed = 0; + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + // Is this facility here? + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + // Increase tally + ubFacilityTrainersAllowed += gFacilityTypes[cnt].ubMilitiaTrainersAllowed; + } + } + + // If we are here, then TrainersAllowed > 0. + // Otherwise we'd have failed the BasicCanTrain check + if ( CountMilitiaTrainersInSoldiersSector( pSoldier, TOWN_MILITIA ) >= ubFacilityTrainersAllowed ) + { + swprintf( sString, gzLateLocalizedString[ 47 ], ubFacilityTrainersAllowed ); + + // Report "Too many Militia Trainers!" + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + // No errors to report. Character can perform this assignment. + return (TRUE); + +} + + +BOOLEAN CanCharacterTrainMobileMilitiaWithErrorReport( SOLDIERTYPE *pSoldier ) +{ + // Temp string. + CHAR16 sString[ 256 ]; + INT32 iCounter = 0; + + // Enemies present? + if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + // Report "Enemies present!" + DoScreenIndependantMessageBox( New113HAMMessage[5], MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + + /////////////////////////////// + // Test for required Leadership + + UINT16 usEffectiveLeadership = pSoldier->stats.bLeadership; // Basic leadership score + BOOLEAN fSufficientLeadership = TRUE; // Result of check + + // Apply modifier for TEACHER trait, if that feature is activated + if ( gGameExternalOptions.usTeacherTraitEffectOnLeadership > 0 && gGameExternalOptions.usTeacherTraitEffectOnLeadership != 100 ) + { + // Modifier applied once for each TEACHING level. + for (UINT8 i = 0; i < NUM_SKILL_TRAITS( pSoldier, TEACHING ); i++ ) + { + // This is a percentage modifier. + usEffectiveLeadership = (usEffectiveLeadership * gGameExternalOptions.usTeacherTraitEffectOnLeadership)/100; + } + usEffectiveLeadership = __min(100,usEffectiveLeadership); + } + + // Is there an INI-set requirement? + if (gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia) + { + // Does he fail the requirement? + if (usEffectiveLeadership < gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia) + { + fSufficientLeadership = FALSE; + } + } + // If there is no requirement, does the soldier have ANY leadership skill? + else if (usEffectiveLeadership <= 0) + { + fSufficientLeadership = FALSE; + } + + // Failed above leadership tests? + if( !fSufficientLeadership ) + { + // Report "Insufficient Leadership Skill" + swprintf(sString, New113HAMMessage[6], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return ( FALSE ); + } + + ///////////////////////////////////////// + // Town loyalty test + // is the current loyalty high enough to train militia at all? + if( DoesSectorMercIsInHaveSufficientLoyaltyToTrainMilitia( pSoldier ) == FALSE ) + { + // Report "Not enough loyalty!" + DoScreenIndependantMessageBox( zMarksMapScreenText[ 20 ], MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + //////////////////////////////////////////////// + // Check whether controlled town sectors already have full militia + + INT8 bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR(pSoldier->sSectorX, pSoldier->sSectorY) ] ); + UINT8 ubUnfullSectorsFound = 0; + + if ( CountMilitia( pSectorInfo ) < gGameExternalOptions.iMaxMilitiaPerSector ) + { + ubUnfullSectorsFound++; + } + else + { + iCounter = 0; + while( pTownNamesList[ iCounter ] != 0 ) + { + // Are we in this city? + if( pTownNamesList[ iCounter] == bTownId ) + { + INT16 sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + INT16 sCurrentY = GET_Y_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + + pSectorInfo = &( SectorInfo[ SECTOR(sCurrentX, sCurrentY) ] ); + // 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. This sector cannot generate militia anyway. + iCounter++; + continue; + } + else + { + if (CountMilitia(pSectorInfo) < gGameExternalOptions.iMaxMilitiaPerSector ) + { + // Found a controlled city sector that does not yet have a full garrison + ubUnfullSectorsFound++; + } + } + } + iCounter++; + } + } + if (ubUnfullSectorsFound) + { + // At least one city sector is controlled but not full of garrison militia. Can't train mobiles! + swprintf(sString, New113HAMMessage[9], gGameExternalOptions.iMaxMilitiaPerSector, pTownNames[bTownId]); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + ////////////////////////////////////////// + // Capacity and Garrison checks in nearby sectors + UINT16 pMoveDir[4][3]; + UINT8 uiDirNumber = 0; + BOOLEAN fFoundValidSector = FALSE; + + GenerateDirectionInfosForTraining( pSoldier->sSectorX, pSoldier->sSectorY, &uiDirNumber, pMoveDir ); + // Found at least one suitable place to put Mobiles? + if (uiDirNumber) + { + fFoundValidSector = TRUE; + } + // Try entire city. Only the HAM Smart Militia Generator can handle this. + else + { + iCounter = 0; + // Go through each city in the game + while( pTownNamesList[ iCounter ] != 0 ) + { + // Are we in this city? + if( pTownNamesList[ iCounter] == bTownId ) + { + INT16 sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + INT16 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. This sector cannot generate militia anyway. + iCounter++; + continue; + } + + // Find out if any adjacent sectors have room in them. + GenerateDirectionInfosForTraining( sCurrentX, sCurrentY, &uiDirNumber, pMoveDir ); + + if(uiDirNumber) + { + fFoundValidSector = TRUE; + break; + } + } + iCounter++; + } + } + + // Couldn't find at least one sector to place Mobiles. Report "No room!" + if (!fFoundValidSector) + { + swprintf(sString, New113HAMMessage[8], pTownNames[ bTownId ]); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + + + ////////////////////////////////////////////// + // HEADROCK HAM 3.5: Militia Training Facility + // + // Militia training is enabled in the sector only if there is a facility that allows this here. + // If one or more facilities are found, positive values are summed up and presented as the number + // of trainers allowed in the sector. Values are read from XML, and can be set to mimic JA2 + // defaults. This renders the INI setting "MAX_MILITIA_TRAINERS.." obsolete. + + UINT8 ubFacilityTrainersAllowed = 0; + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + // Is this facility here? + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + // Increase tally + ubFacilityTrainersAllowed += gFacilityTypes[cnt].ubMobileMilitiaTrainersAllowed; + } + } + // If we are here, then TrainersAllowed > 0. + // Otherwise we'd have failed the BasicCanTrain check + if ( CountMilitiaTrainersInSoldiersSector( pSoldier, MOBILE_MILITIA ) >= ubFacilityTrainersAllowed ) + { + swprintf( sString, New113HAMMessage[ 7 ], ubFacilityTrainersAllowed ); + // Report "Too many Militia Trainers!" + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + // No errors to report. Character can perform this assignment. + return (TRUE); + +} +// HEADROCK HAM 3.6: This function determines whether a character can use facilities at all, or whether the sector has +// any "useable" facilities at all. +BOOLEAN BasicCanCharacterFacility( SOLDIERTYPE *pSoldier ) +{ + ///////////////////////////////////////////////////// + // Tests whether character can do assignments at all! + + AssertNotNIL(pSoldier); + + if ( !BasicCanCharacterAssignment( pSoldier, TRUE ) ) + { + return( FALSE ); + } + + // Is character dead or unconscious? + if( pSoldier->stats.bLife < OKLIFE ) + { + // dead or unconscious... + return ( FALSE ); + } + + // Is character underground? + if( pSoldier->bSectorZ != 0 ) + { + // underground training is not allowed (code doesn't support and it's a reasonable enough limitation) + return( FALSE ); + } + + // Is character on the way into/out of Arulco? + if( IsCharacterInTransit( pSoldier ) == TRUE ) + { + return ( FALSE ); + } + + // Is character travelling between sectors? + if( CharacterIsBetweenSectors( pSoldier ) ) + { + return( FALSE ); + } + + // Is character an Escortee? + if( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__EPC ) + { + // epcs can't do this + return( FALSE ); + } + + // Is character a Vehicle or Robot? + if ( ( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) || AM_A_ROBOT( pSoldier ) ) + { + return( FALSE ); + } + + // IS character inside a helicopter over a hostile sector? + if( pSoldier->bAssignment == VEHICLE ) + { + if( ( iHelicopterVehicleId != -1 ) && ( pSoldier->iVehicleId == iHelicopterVehicleId ) ) + { + // enemies in sector + if( NumEnemiesInSector( pSoldier->sSectorX, pSoldier->sSectorY ) > 0 ) + { + return( FALSE ); + } + } + } + + //////////////////////////////////////////////////////////////////////// + // Tests to see whether this sector contains any facilities that could be used at all. + + UINT8 ubSector = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + BOOLEAN fFoundUseableFacility = FALSE; + + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + if (gFacilityLocations[ubSector][cnt].fFacilityHere) + { + if (gFacilityTypes[cnt].ubTotalStaffLimit) + { + fFoundUseableFacility = TRUE; + } + } + } + if (!fFoundUseableFacility) + { + // No useable facilities in this sector. + return FALSE; + } + + return ( TRUE ); +} + +BOOLEAN DisplayFacilityMenu( SOLDIERTYPE *pSoldier ) +{ + BOOLEAN fFacilityPresent=FALSE; + INT32 iCounter=0; + INT32 hStringHandle=0; + + // first, clear pop up box + RemoveBox(ghFacilityBox); + ghFacilityBox = -1; + + CreateFacilityBox(); + SetCurrentBox(ghFacilityBox); + + // run through list of staff/use facilities in sector and add them to pop up box + for ( iCounter = 0; iCounter < MAX_NUM_FACILITY_TYPES; iCounter++ ) + { + if ( gFacilityLocations[ SECTOR(pSoldier->sSectorX,pSoldier->sSectorY) ][iCounter].fFacilityHere ) + { + if ( gFacilityTypes[ iCounter ].ubTotalStaffLimit ) + { + AddMonoString((UINT32 *)&hStringHandle, gFacilityTypes[ iCounter ].szFacilityName); + fFacilityPresent = TRUE; + } + } + } + + if (!fFacilityPresent) + { + return FALSE; + } + + // cancel string (borrow the one in the squad menu) + AddMonoString((UINT32 *)&hStringHandle, pSquadMenuStrings[ SQUAD_MENU_CANCEL ]); + + SetBoxFont(ghFacilityBox, MAP_SCREEN_FONT); + SetBoxHighLight(ghFacilityBox, FONT_WHITE); + SetBoxShade(ghFacilityBox, FONT_GRAY7); + SetBoxForeground(ghFacilityBox, FONT_LTGREEN); + SetBoxBackground(ghFacilityBox, FONT_BLACK); + + ResizeBoxToText( ghFacilityBox ); + + CheckAndUpdateTacticalAssignmentPopUpPositions( ); + + return TRUE; +} + +BOOLEAN DisplayFacilityAssignmentMenu( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType ) +{ + + if (!gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][ubFacilityType].fFacilityHere) + { + // Facility isn't here? Odd. + return (FALSE); + } + + INT32 iCounter=0; + INT32 iCounterB = 0; + INT32 hStringHandle=0; + CHAR16 sTempString[128]; + BOOLEAN fFoundVehicle; + + // first, clear pop up box + RemoveBox(ghFacilityAssignmentBox); + ghFacilityAssignmentBox = -1; + + CreateFacilityAssignmentBox(); + SetCurrentBox(ghFacilityAssignmentBox); + + BOOLEAN fAssignmentsFound = FALSE; + // Create a list of possible assignments at this facility + for ( iCounter = 0; iCounter < NUM_FACILITY_ASSIGNMENTS; iCounter++ ) + { + if ( gFacilityTypes[ ubFacilityType ].AssignmentData[iCounter].ubStaffLimit ) + { + if ( iCounter == FAC_REPAIR_VEHICLE ) + { + // Test to see whether there are any. + for ( iCounterB = 0; iCounterB < ubNumberOfVehicles; iCounterB++ ) + { + if ( pVehicleList[iCounterB].fValid == TRUE ) + { + if ( IsThisVehicleAccessibleToSoldier( pSoldier, iCounterB ) ) + { + // Create line that says "Repair X" where X is the vehicle. + swprintf( sTempString, gzFacilityAssignmentStrings[ FAC_REPAIR_VEHICLE ], pVehicleStrings[ pVehicleList[ iCounterB ].ubVehicleType ]); + AddMonoString((UINT32 *)&hStringHandle, sTempString ); + fFoundVehicle = TRUE; + } + } + } + if (fFoundVehicle = FALSE) + { + // Create line that says "Repair Vehicle", and will be shaded. + swprintf( sTempString, gzFacilityAssignmentStrings[ FAC_REPAIR_VEHICLE ], L"Vehicle" ); + AddMonoString((UINT32 *)&hStringHandle, sTempString ); + } + } + else if ( iCounter == FAC_REPAIR_ROBOT ) + { + // is the ROBOT here? + if( IsRobotInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + // robot + AddMonoString((UINT32 *)&hStringHandle, gzFacilityAssignmentStrings[ FAC_REPAIR_ROBOT ] ); + } + } + else + { + AddMonoString((UINT32 *)&hStringHandle, gzFacilityAssignmentStrings[ iCounter ]); + } + fAssignmentsFound = TRUE; + } + } + + if (!fAssignmentsFound) + { + return FALSE; + } + + // cancel string (borrow the one in the squad menu) + AddMonoString((UINT32 *)&hStringHandle, pSquadMenuStrings[ SQUAD_MENU_CANCEL ]); + + SetBoxFont(ghFacilityAssignmentBox, MAP_SCREEN_FONT); + SetBoxHighLight(ghFacilityAssignmentBox, FONT_WHITE); + SetBoxShade(ghFacilityAssignmentBox, FONT_GRAY7); + SetBoxForeground(ghFacilityAssignmentBox, FONT_LTGREEN); + SetBoxBackground(ghFacilityAssignmentBox, FONT_BLACK); + + ResizeBoxToText( ghFacilityAssignmentBox ); + + CheckAndUpdateTacticalAssignmentPopUpPositions( ); + + // Set the current facility whose assignments are being shown + gubFacilityInSubmenu = ubFacilityType; + + return TRUE; +} + +// HEADROCK HAM 3.6: Create the facility menu box. +void CreateFacilityBox() +{ + FacilityPosition.iX = OrigFacilityPosition.iX; + + if( giBoxY != 0 ) + { + FacilityPosition.iY = giBoxY + ( ASSIGN_MENU_FACILITY * GetFontHeight( MAP_SCREEN_FONT ) ); + } + + CreatePopUpBox(&ghFacilityBox, FacilityDimensions, FacilityPosition, (POPUP_BOX_FLAG_CLIP_TEXT|POPUP_BOX_FLAG_CENTER_TEXT|POPUP_BOX_FLAG_RESIZE )); + SetBoxBuffer(ghFacilityBox, FRAME_BUFFER); + SetBorderType(ghFacilityBox,guiPOPUPBORDERS); + SetBackGroundSurface(ghFacilityBox, guiPOPUPTEX); + SetMargins( ghFacilityBox, 6, 6, 4, 4 ); + SetLineSpace(ghFacilityBox, 2); + + // resize box to text + ResizeBoxToText( ghFacilityBox ); + + DetermineBoxPositions( ); +} + +void CreateDestroyMouseRegionForFacilityMenu( void ) +{ + static BOOLEAN fCreated = FALSE; + + UINT32 uiMenuLine = 0; + INT32 iFacilityType = 0; + INT32 iFontHeight = 0; + INT32 iBoxXPosition = 0; + INT32 iBoxYPosition = 0; + SGPPoint pPosition, pPoint; + INT32 iBoxWidth = 0; + SGPRect pDimensions; + SOLDIERTYPE *pSoldier = NULL; + + + if( fShowFacilityMenu ) + { + GetBoxPosition( ghAssignmentBox, &pPoint); + + // get dimensions..mostly for width + GetBoxSize( ghAssignmentBox, &pDimensions ); + + // vehicle position + FacilityPosition.iX = pPoint.iX + pDimensions.iRight; + + SetBoxPosition( ghFacilityBox , FacilityPosition ); + } + + + if( ( fShowFacilityMenu == TRUE ) && ( fCreated == FALSE ) ) + { + CheckAndUpdateTacticalAssignmentPopUpPositions( ); + + // grab height of font + iFontHeight = GetLineSpace( ghFacilityBox ) + GetFontHeight( GetBoxFont( ghFacilityBox ) ); + + // get x.y position of box + GetBoxPosition( ghFacilityBox, &pPosition); + + // grab box x and y position + iBoxXPosition = pPosition.iX; + iBoxYPosition = pPosition.iY; + + // get dimensions..mostly for width + GetBoxSize( ghFacilityBox, &pDimensions ); + SetBoxSecondaryShade( ghFacilityBox, FONT_YELLOW ); + + // get width + iBoxWidth = pDimensions.iRight; + + SetCurrentBox( ghFacilityBox ); + + pSoldier = GetSelectedAssignSoldier( FALSE ); + + // define regions + //for( iCounter = 0; iCounter < GetNumberOfLinesOfTextInBox( ghFacilityBox ); iCounter++ ) + // run through list of staff/use facilities in sector and add them to pop up box + for ( UINT32 iCounter = 0; iCounter < MAX_NUM_FACILITY_TYPES; iCounter++ ) + { + if ( gFacilityLocations[ SECTOR(pSoldier->sSectorX,pSoldier->sSectorY) ][iCounter].fFacilityHere ) + { + if ( gFacilityTypes[ iCounter ].ubTotalStaffLimit ) + { + // add mouse region for each facility + MSYS_DefineRegion( &gFacilityMenuRegion[ uiMenuLine ], ( INT16 )( iBoxXPosition ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * uiMenuLine ), ( INT16 )( iBoxXPosition + iBoxWidth ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * ( uiMenuLine + 1 ) ), MSYS_PRIORITY_HIGHEST - 4 , + MSYS_NO_CURSOR, FacilityMenuMvtCallback, FacilityMenuBtnCallback ); + + MSYS_SetRegionUserData( &gFacilityMenuRegion[ uiMenuLine ], 0, uiMenuLine ); + // store facility ID in the SECOND user data + + MSYS_SetRegionUserData( &gFacilityMenuRegion[ uiMenuLine ], 1, iCounter ); + + uiMenuLine++; + } + } + } + + // cancel line + MSYS_DefineRegion( &gFacilityMenuRegion[ uiMenuLine ], ( INT16 )( iBoxXPosition ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * uiMenuLine ), ( INT16 )( iBoxXPosition + iBoxWidth ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * ( uiMenuLine + 1 ) ), MSYS_PRIORITY_HIGHEST - 4 , + MSYS_NO_CURSOR, FacilityMenuMvtCallback, FacilityMenuBtnCallback ); + MSYS_SetRegionUserData( &gFacilityMenuRegion[ uiMenuLine ], 0, uiMenuLine ); + MSYS_SetRegionUserData( &gFacilityMenuRegion[ uiMenuLine ], 1, MAX_NUM_FACILITY_TYPES ); + + // created + fCreated = TRUE; + + // pause game + PauseGame( ); + + // unhighlight all strings in box + UnHighLightBox( ghFacilityBox ); + + fCreated = TRUE; + + //HandleShadingOfLinesForFacilityMenu( ); + } + else if( ( ( fShowFacilityMenu == FALSE ) || ( fShowAssignmentMenu == FALSE ) ) && ( fCreated == TRUE ) ) + { + fCreated = FALSE; + + // remove these regions + for( uiMenuLine = 0; uiMenuLine < GetNumberOfLinesOfTextInBox( ghFacilityBox ); uiMenuLine++ ) + { + MSYS_RemoveRegion( &gFacilityMenuRegion[ uiMenuLine ] ); + } + + fShowFacilityMenu = FALSE; + + SetRenderFlags( RENDER_FLAG_FULL ); + + HideBox( ghFacilityBox ); + + if ( fShowAssignmentMenu ) + { + // remove highlight on the parent menu + UnHighLightBox( ghAssignmentBox ); + } + } + + return; +} + +void FacilityMenuMvtCallback(MOUSE_REGION * pRegion, INT32 iReason ) +{ + // mvt callback handler for assignment region + INT32 iValue = -1; + + iValue = MSYS_GetRegionUserData( pRegion, 0 ); + + if( HandleAssignmentExpansionAndHighLightForFacilityMenu( ) == TRUE ) + { + return; + } + + if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) + { + if( iValue < MAX_NUM_FACILITY_TYPES ) + { + // no shaded(disabled) lines actually appear in vehicle menus + if( GetBoxShadeFlag( ghFacilityBox, iValue ) == FALSE ) + { + // highlight vehicle line + HighLightBoxLine( ghFacilityBox, iValue ); + } + } + else + { + // highlight cancel line + HighLightBoxLine( ghFacilityBox, GetNumberOfLinesOfTextInBox( ghFacilityBox ) - 1 ); + } + } + else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) + { + // unhighlight all strings in box + UnHighLightBox( ghFacilityBox ); + + //HandleShadingOfLinesForVehicleMenu( ); + } +} + +void FacilityMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ) +{ + // btn callback handler for assignment region + INT32 iValue = -1; + SOLDIERTYPE * pSoldier = NULL; + BOOLEAN fCanOperateFacility = TRUE; + + pSoldier = GetSelectedAssignSoldier( FALSE ); + + iValue = MSYS_GetRegionUserData( pRegion, 1 ); + + if( ( iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) || ( iReason & MSYS_CALLBACK_REASON_RBUTTON_DWN ) ) + { + if ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) && !fShowMapInventoryPool ) + { + UnMarkButtonDirty( giMapBorderButtons[ MAP_BORDER_TOWN_BTN ] ); + } + } + + if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + + if( fShowFacilityAssignmentMenu ) + { + // cancel Facility submenu + fShowFacilityAssignmentMenu = FALSE; + // Reset facility number + gubFacilityInSubmenu = 0; + gubFacilityLineForSubmenu = 0; + // rerender tactical stuff + gfRenderPBInterface = TRUE; + + return; + } + + if (iValue > 0 && iValue < MAX_NUM_FACILITY_TYPES) + { + // The line we clicked will open a new menu adjacent to this one. This menu lists all possible assignments + // that can be performed at this facility. + gubFacilityInSubmenu = (INT8)iValue; + gubFacilityLineForSubmenu = (UINT8) MSYS_GetRegionUserData( pRegion, 0 ); + + fShowFacilityAssignmentMenu = TRUE; + DetermineBoxPositions(); + + DisplayFacilityAssignmentMenu( pSoldier, iValue ); + + + // For now, only tests whether the character can use facilities at all, or whether there ARE facilities + // to be used at the moment. + if( !BasicCanCharacterFacility(pSoldier) ) + { + // No feedback. The menu options should be greyed out, anyway. + return; + } + } + + + + + + + + /*// Check for standing debt for Facility Operation. May trigger an on-screen prompt or error message. + if ( gFacilityTypes[iValue].AssignmentData[FAC_STAFF].sCostPerHour && // Facility costs money to operate + !IsOutstandingFacilityDebtWithErrorReport() ) // There's an outstanding debt for facilities + { + // Facility debt needs to be paid first. + return; + } + + // PASSED BOTH TESTS - ALLOW SOLDIER TO TRAIN MILITIA HERE + + pSoldier->bOldAssignment = pSoldier->bAssignment; + + if( ( pSoldier->bAssignment != FACILITY_STAFF ) ) + { + SetTimeOfAssignmentChangeForMerc( pSoldier ); + } + + MakeSoldiersTacticalAnimationReflectAssignment( pSoldier ); + + // stop showing menu + fShowAssignmentMenu = FALSE; + giAssignHighLine = -1; + + // remove from squad + + if( pSoldier->bOldAssignment == VEHICLE ) + { + TakeSoldierOutOfVehicle( pSoldier ); + } + RemoveCharacterFromSquads( pSoldier ); + + ChangeSoldiersAssignment( pSoldier, FACILITY_STAFF ); + + pSoldier->sFacilityTypeOperated = iValue; + + // assign to a movement group + AssignMercToAMovementGroup( pSoldier ); + if( gFacilityTypes[ iValue ].AssignmentData[FAC_STAFF].sCostPerHour ) + { + // Ask player if he wishes to expend this money. + HandleInterfaceMessageForCostOfOperatingFacility( pSoldier, FAC_STAFF ); + } + else + { + SetAssignmentForList( FACILITY_STAFF, iValue ); + } + gfRenderPBInterface = TRUE;*/ + + else + { + // stop showing menu + fShowFacilityMenu = FALSE; + + // unhighlight the assignment box + UnHighLightBox( ghAssignmentBox ); + + // reset list + ResetSelectedListForMapScreen( ); + gfRenderPBInterface = TRUE; + } + + fTeamPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + } + else if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) + { + if( fShowFacilityAssignmentMenu ) + { + // cancel attribute submenu + fShowFacilityAssignmentMenu = FALSE; + // rerender tactical stuff + gfRenderPBInterface = TRUE; + + return; + } + } +} + +// Check whether a character can staff a specific type of facility in this sector. +BOOLEAN CanCharacterFacility( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType, UINT8 ubAssignmentType ) +{ + AssertNotNIL(pSoldier); + UINT8 ubSectorID = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + BOOLEAN fFoundVehicleToRepair = FALSE; + + // Make sure the basic sector/merc variables are still applicable. This is simply a fail-safe. + if( !BasicCanCharacterFacility( pSoldier ) ) + { + // Soldier/Sector have somehow failed the basic test. Character automatically fails this test as well. + return( FALSE ); + } + + if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + return( FALSE ); + } + + // Facility exists in this sector at all? (Failsafe) + if (!gFacilityLocations[ubSectorID][ubFacilityType].fFacilityHere) + { + // No such facility here! Odd. + return( FALSE ); + } + + ////////////////////////////////////////// + // Does character have sufficient skill? + if (pSoldier->stats.bStrength < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumStrength || + pSoldier->stats.bLife < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumHealth || + pSoldier->stats.bWisdom < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumWisdom || + pSoldier->stats.bAgility < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumAgility || + pSoldier->stats.bDexterity < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumDexterity || + pSoldier->stats.bMarksmanship < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMarksmanship || + pSoldier->stats.bMechanical < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMechanical || + pSoldier->stats.bMedical < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMedical || + pSoldier->stats.bLeadership < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumLeadership || + pSoldier->stats.bExplosive < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumExplosives || + pSoldier->stats.bExpLevel < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumLevel || + + pSoldier->aiData.bMorale < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMorale || + pSoldier->bBreathMax < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumBreath + ) + { + // Character is lacking a stat required for this specific assignment. + return ( FALSE ); + } + + //////////////////////////////////////// + // Check town loyalty + + UINT8 ubTownID = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + if (ubTownID != BLANK_SECTOR) + { + if (gTownLoyalty[ ubTownID ].ubRating < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumLoyaltyHere ) + { + // Insufficient loyalty. + return (FALSE); + } + } + + ////////////////////////////////////////////// + // Check capacity for mercs working at this particular facility. + + // Count the number of open slots left for people trying to perform this assignment in the same facility, in the + // soldier's sector. + // Note that we can reach this when the soldier is ALREADY one of the people working at this facility, so in that + // case count him out. + INT8 bX = 0; + INT8 bY = 0; + if ( ubFacilityType == (UINT8)pSoldier->sFacilityTypeOperated ) + bX = -1; + if ( ubAssignmentType == GetSoldierFacilityAssignmentIndex( pSoldier ) ) + bY = -1; + + if ( CountFreeFacilitySlots( (UINT8)pSoldier->sSectorX, (UINT8)pSoldier->sSectorY, ubFacilityType) <= bX || // Too many people in the facility, or + CountFreeFacilityAssignmentSlots( (UINT8)pSoldier->sSectorX, (UINT8)pSoldier->sSectorY, ubFacilityType, ubAssignmentType ) <= bY ) // Too many people doing this assignment + { + // No free slots. + return (FALSE); + } + + //////////////////////////////////////////////////// + // Check for extra requirements for specific assignments. + + // DOCTOR + if (ubAssignmentType == FAC_DOCTOR) + { + BOOLEAN fFoundMedKit = FALSE; + INT8 bPocket = 0; + + // find med kit + // CHRISL: Changed to dynamically determine max inventory locations. + for (bPocket = HANDPOS; bPocket < NUM_INV_SLOTS; bPocket++) + { + // doctoring is allowed using either type of med kit (but first aid kit halves doctoring effectiveness) + if( IsMedicalKitItem( &( pSoldier -> inv[ bPocket ] ) ) ) + { + fFoundMedKit = TRUE; + break; + } + } + + if( fFoundMedKit == FALSE ) + { + return( FALSE ); + } + } + + // REPAIR ITEMS + if ( ubAssignmentType == FAC_REPAIR_ITEMS || + ubAssignmentType == FAC_REPAIR_VEHICLE || + ubAssignmentType == FAC_REPAIR_ROBOT ) + { + // make sure he has a toolkit + if ( FindToolkit( pSoldier ) == NO_SLOT ) + { + return( FALSE ); + } + + switch (ubAssignmentType) + { + case FAC_REPAIR_ITEMS: + // items? + if ( !DoesCharacterHaveAnyItemsToRepair( pSoldier, FINAL_REPAIR_PASS ) ) + { + return( FALSE ); + } + break; + case FAC_REPAIR_VEHICLE: + for ( INT32 iCounter = 0; iCounter < ubNumberOfVehicles; iCounter++ ) + { + if ( pVehicleList[ iCounter ].fValid == TRUE ) + { + // the helicopter, is NEVER repairable... + if ( iCounter != iHelicopterVehicleId ) + { + if ( IsThisVehicleAccessibleToSoldier( pSoldier, iCounter ) ) + { + if( CanCharacterRepairVehicle( pSoldier, iCounter ) == TRUE ) + { + // there is a repairable vehicle here + fFoundVehicleToRepair = TRUE; + } + } + } + } + } + if (!fFoundVehicleToRepair) + { + return (FALSE); + } + break; + case FAC_REPAIR_ROBOT: + SOLDIERTYPE *pRobot = NULL; + + // do we in fact have the robot on the team? + pRobot = GetRobotSoldier( ); + if( pRobot == NULL ) + { + return( FALSE ); + } + + // if robot isn't damaged at all + if( pRobot->stats.bLife == pRobot->stats.bLifeMax ) + { + return( FALSE ); + } + + // is the robot in the same sector + if( IsRobotInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) == FALSE ) + { + return( FALSE ); + } + break; + } + } + + // If we've reached this, then all is well. + return( TRUE ); + +} + +INT8 CountFreeFacilitySlots( UINT8 sMapX, UINT8 sMapY, UINT8 ubFacilityType ) +{ + INT16 sFreeSlotsFound = 0; + + UINT8 ubStaffLimit = gFacilityTypes[ubFacilityType].ubTotalStaffLimit; + UINT8 ubStaffFoundHere = 0; + + if (!gFacilityLocations[SECTOR(sMapX,sMapY)][ubFacilityType].fFacilityHere) + { + // The facility is not present! + return (0); + } + + if (!ubStaffLimit) + { + // No people are allowed to work at this facility at all! + return (0); + } + else + { + UINT8 ubCounter = 0; + SOLDIERTYPE *pSoldier; + // Count number of people doing anything at this facility. + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character operating this facility? + if( (UINT8)pSoldier->sFacilityTypeOperated == ubFacilityType && + pSoldier->sSectorX == sMapX && // Is he in the same sector? + pSoldier->sSectorY == sMapY ) + { + // Increase tally. + ubStaffFoundHere++; + } + ubCounter++; + } + sFreeSlotsFound = ubStaffLimit - ubStaffFoundHere; + return ((INT8)sFreeSlotsFound); + } +} + +INT8 CountFreeFacilityAssignmentSlots( UINT8 sMapX, UINT8 sMapY, UINT8 ubFacilityType, UINT8 ubAssignmentIndex ) +{ + INT16 sFreeSlotsFound = 0; + + UINT8 ubStaffLimit = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentIndex].ubStaffLimit; + UINT8 ubStaffFoundHere = 0; + + if (!gFacilityLocations[SECTOR(sMapX,sMapY)][ubFacilityType].fFacilityHere) + { + // The facility is not present! + return (0); + } + + if (!ubStaffLimit) + { + // No people are allowed to perform this assignment at this facility at all! + return (0); + } + else + { + UINT8 ubCounter = 0; + SOLDIERTYPE *pSoldier; + + // Count number of people doing this assignment at this facility. + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character operating this facility? + if( (UINT8)pSoldier->sFacilityTypeOperated == ubFacilityType && + pSoldier->sSectorX == sMapX && // Is he in the same sector? + pSoldier->sSectorY == sMapY ) + { + // Is he performing the same exact assignment we're looking to do? + if (GetSoldierFacilityAssignmentIndex( pSoldier ) == ubAssignmentIndex) + { + // Increase tally. + ubStaffFoundHere++; + } + } + ubCounter++; + } + sFreeSlotsFound = ubStaffLimit - ubStaffFoundHere; + return ((INT8)sFreeSlotsFound); + } +} + + +// Check whether a character can staff a specific type of facility in this sector. Display a message informing player +// of specific obstacles. +BOOLEAN CanCharacterFacilityWithErrorReport( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType, UINT8 ubAssignmentType ) +{ + CHAR16 sString[ 256 ]; + AssertNotNIL(pSoldier); + UINT8 ubSectorID = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + BOOLEAN fFoundVehicleToRepair = FALSE; + + // Make sure the basic sector/merc variables are still applicable. This is simply a fail-safe. + if( !BasicCanCharacterFacility( pSoldier ) ) + { + // Soldier/Sector have somehow failed the basic test. Character automatically fails this test as well. + return( FALSE ); + } + + if( NumEnemiesInAnySector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + DoScreenIndependantMessageBox( New113HAMMessage[10], MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + + // Facility exists in this sector at all? (Failsafe) + if (!gFacilityLocations[ubSectorID][ubFacilityType].fFacilityHere) + { + // No such facility here! Odd. + return( FALSE ); + } + + ////////////////////////////////////////// + // Skill/Condition check + + if (pSoldier->stats.bStrength < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumStrength) + { + swprintf(sString, gzFacilityErrorMessage[0], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bDexterity < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumDexterity) + { + swprintf(sString, gzFacilityErrorMessage[1], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bAgility < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumAgility) + { + swprintf(sString, gzFacilityErrorMessage[2], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bLife < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumHealth) + { + swprintf(sString, gzFacilityErrorMessage[3], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bWisdom < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumWisdom) + { + swprintf(sString, gzFacilityErrorMessage[4], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bMarksmanship < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMarksmanship) + { + swprintf(sString, gzFacilityErrorMessage[5], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bMedical < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMedical) + { + swprintf(sString, gzFacilityErrorMessage[6], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bMechanical < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMechanical) + { + swprintf(sString, gzFacilityErrorMessage[7], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bLeadership < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumLeadership) + { + swprintf(sString, gzFacilityErrorMessage[8], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bExplosive < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumExplosives) + { + swprintf(sString, gzFacilityErrorMessage[9], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->stats.bExpLevel < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumLevel) + { + swprintf(sString, gzFacilityErrorMessage[10], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->aiData.bMorale < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumMorale) + { + swprintf(sString, gzFacilityErrorMessage[11], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + if (pSoldier->bBreathMax < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumBreath) + { + swprintf(sString, gzFacilityErrorMessage[12], pSoldier->name); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + + //////////////////////////////////////// + // Check town loyalty + + UINT8 ubTownID = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + if (ubTownID != BLANK_SECTOR) + { + if (gTownLoyalty[ ubTownID ].ubRating < gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMinimumLoyaltyHere ) + { + swprintf(sString, gzFacilityErrorMessage[13], pTownNames[ ubTownID ]); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + // Insufficient loyalty. + return (FALSE); + } + } + + ////////////////////////////////////////////// + // Check capacity for mercs working at this particular facility. + + // Count the number of open slots left for people trying to perform this assignment in the same facility, in the + // soldier's sector. + // Note that we can reach this when the soldier is ALREADY one of the people working at this facility, so in that + // case count him out. + INT8 bX = 0; + INT8 bY = 0; + if ( ubFacilityType == (UINT8)pSoldier->sFacilityTypeOperated ) + bX = -1; + if ( ubAssignmentType == GetSoldierFacilityAssignmentIndex( pSoldier ) ) + bY = -1; + + if ( CountFreeFacilitySlots( (UINT8)pSoldier->sSectorX, (UINT8)pSoldier->sSectorY, ubFacilityType) <= bX ) + { + // Too many people working at this facility (overall) + swprintf(sString, gzFacilityErrorMessage[14], gFacilityTypes[ubFacilityType].szFacilityName); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + else if (CountFreeFacilityAssignmentSlots( (UINT8)pSoldier->sSectorX, (UINT8)pSoldier->sSectorY, ubFacilityType, ubAssignmentType ) <= bY ) + { + // Too many people performing this specific assignment at this facility. + swprintf(sString, gzFacilityErrorMessage[15], gFacilityTypes[ubFacilityType].szFacilityName); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return (FALSE); + } + + //////////////////////////////////////////////////// + // Check for extra requirements for specific assignments. + + // DOCTOR + if (ubAssignmentType == FAC_DOCTOR) + { + BOOLEAN fFoundMedKit = FALSE; + INT8 bPocket = 0; + + // find med kit + // CHRISL: Changed to dynamically determine max inventory locations. + for (bPocket = HANDPOS; bPocket < NUM_INV_SLOTS; bPocket++) + { + // doctoring is allowed using either type of med kit (but first aid kit halves doctoring effectiveness) + if( IsMedicalKitItem( &( pSoldier -> inv[ bPocket ] ) ) ) + { + fFoundMedKit = TRUE; + break; + } + } + + if( fFoundMedKit == FALSE ) + { + swprintf( sString, zMarksMapScreenText[19], pSoldier->name ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + } + + if ( ubAssignmentType == FAC_REPAIR_ITEMS || + ubAssignmentType == FAC_REPAIR_VEHICLE || + ubAssignmentType == FAC_REPAIR_ROBOT ) + { + // make sure he has a toolkit + if ( FindToolkit( pSoldier ) == NO_SLOT ) + { + swprintf( sString, zMarksMapScreenText[18], pSoldier->name ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + + switch (ubAssignmentType) + { + case FAC_REPAIR_ITEMS: + // items? + if ( !DoesCharacterHaveAnyItemsToRepair( pSoldier, FINAL_REPAIR_PASS ) ) + { + swprintf( sString, gzFacilityErrorMessage[16], pSoldier->name ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return( FALSE ); + } + break; + case FAC_REPAIR_VEHICLE: + for ( INT32 iCounter = 0; iCounter < ubNumberOfVehicles; iCounter++ ) + { + if ( pVehicleList[ iCounter ].fValid == TRUE ) + { + // the helicopter, is NEVER repairable... + if ( iCounter != iHelicopterVehicleId ) + { + if ( IsThisVehicleAccessibleToSoldier( pSoldier, iCounter ) ) + { + if( CanCharacterRepairVehicle( pSoldier, iCounter ) == TRUE ) + { + // there is a repairable vehicle here + fFoundVehicleToRepair = TRUE; + } + } + } + } + } + if (!fFoundVehicleToRepair) + { + // No message. Will be greyed out anyway. + return (FALSE); + } + break; + case FAC_REPAIR_ROBOT: + SOLDIERTYPE *pRobot = NULL; + + // do we in fact have the robot on the team? + pRobot = GetRobotSoldier( ); + if( pRobot == NULL ) + { + return( FALSE ); + } + + // if robot isn't damaged at all + if( pRobot->stats.bLife == pRobot->stats.bLifeMax ) + { + return( FALSE ); + } + + // is the robot in the same sector + if( IsRobotInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) == FALSE ) + { + return( FALSE ); + } + break; + } + } + + // If we've reached this, then all is well. + return( TRUE ); + +} + +void HandleShadingOfLinesForFacilityMenu( void ) +{ + SOLDIERTYPE *pSoldier = NULL; + INT32 iCounter = 0; + INT32 iNumLine = 0; + + if( ( fShowFacilityMenu == FALSE ) || ( ghFacilityBox == -1 ) ) + { + return; + } + + pSoldier = GetSelectedAssignSoldier( FALSE ); + + + // PLEASE NOTE: make sure any changes you do here are reflected in all 3 routines which must remain in synch: + // CreateDestroyMouseRegionForRepairMenu(), DisplayRepairMenu(), and HandleShadingOfLinesForRepairMenu(). + + // run through list of staff/use facilities in sector and add them to pop up box + for ( iCounter = 0; iCounter < MAX_NUM_FACILITY_TYPES; iCounter++ ) + { + if ( gFacilityLocations[ SECTOR(pSoldier->sSectorX,pSoldier->sSectorY) ][iCounter].fFacilityHere ) + { + if ( gFacilityTypes[ iCounter ].ubTotalStaffLimit ) + { + if (BasicCanCharacterFacility( pSoldier )) + { + UnShadeStringInBox( ghFacilityBox, iNumLine ); + UnSecondaryShadeStringInBox( ghFacilityBox, iNumLine ); + // No secondary shade. Facilities are either shaded or unshaded. Specific + // assignments for this facility might be secondary-shaded though. + } + else + { + UnSecondaryShadeStringInBox( ghFacilityBox, iNumLine ); + ShadeStringInBox( ghFacilityBox, iNumLine ); + } + iNumLine++; + } + } + } + return; +} + +void HandleInterfaceMessageForCostOfOperatingFacility( SOLDIERTYPE *pSoldier, UINT8 ubAssignmentType ) +{ + // If you hit this assertion, then the soldier was not told to operate any facility before this function + // was called. Generally, it should happen RIGHT BEFORE calling this function! + Assert (pSoldier->sFacilityTypeOperated != -1); + + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + INT32 iFacilityOperatingCost = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour; + gpFacilityStaffer = pSoldier; + + CHAR16 sString[ 128 ]; + SGPRect pCenteringRect= {0, 0, 640, INV_INTERFACE_START_Y }; + + swprintf( sString, New113HAMMessage[13], iFacilityOperatingCost ); + + // if we are in mapscreen, make a pop up + if( guiCurrentScreen == MAP_SCREEN ) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayFacilityCostsYesNoBoxCallback ); + } + else + { + DoMessageBox( MSG_BOX_BASIC_STYLE, sString, GAME_SCREEN, MSG_BOX_FLAG_YESNO, PayFacilityCostsYesNoBoxCallback, &pCenteringRect ); + } + + return; +} + +// HEADROCK HAM 3.6: Callback on agreeing to continue facility staffing. +void PayFacilityCostsYesNoBoxCallback( UINT8 bExitValue ) +{ + // yes + if( bExitValue == MSG_BOX_RETURN_YES ) + { + // reset staffer variable. We've already set his assignment so everything's good. + gpFacilityStaffer = NULL; + } + else if( bExitValue == MSG_BOX_RETURN_NO ) + { + StopTimeCompression(); + + FacilityStaffingRejected(); + } + + return; +} + +// HEADROCK HAM 3.6: Callback on agreeing to pay off facility debts before you can assign another character to facility work. +void PayFacilityDebtManuallyYesNoBoxCallback( UINT8 bExitValue ) +{ + // This callback should only be called if the player can pay off the ENTIRE debt!! + Assert( LaptopSaveInfo.iCurrentBalance >= giTotalOwedForFacilityOperationsToday ); + + // yes + if( bExitValue == MSG_BOX_RETURN_YES ) + { + // Pay total debt. + INT32 iToPay = giTotalOwedForFacilityOperationsToday; + AddTransactionToPlayersBook( FACILITY_OPERATIONS, 0, GetWorldTotalMin(), -( iToPay ) ); + giTotalOwedForFacilityOperationsToday = 0; + gfOutstandingFacilityDebt = FALSE; + + // reset staffer variable. We've already set his assignment so everything's good. + gpFacilityStaffer = NULL; + } + else if( bExitValue == MSG_BOX_RETURN_NO ) + { + StopTimeCompression(); + } + + return; +} + +// IMPORTANT: Rejected player prompt to pay hourly for using a facility. +void FacilityStaffingRejected( ) +{ + // take the selected merc off Facility work. + gpFacilityStaffer->sFacilityTypeOperated = -1; + AddCharacterToAnySquad( gpFacilityStaffer ); + + // this completes the facility costs prompt sequence + gpFacilityStaffer = NULL; +} + +// Resets all assignments for characters working at facilities that cost money to operate. This is run whenever the +// player incurs an unpayable debt for facility operation. +void ResetAllExpensiveFacilityAssignments() +{ + SOLDIERTYPE *pSoldier; + UINT8 ubCounter = 0; + + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character doing facility work? + UINT8 ubAssignmentIndex = GetSoldierFacilityAssignmentIndex( pSoldier ); + if( ubAssignmentIndex != -1 ) + { + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + // Does facility cost money to operate? + if (gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentIndex].sCostPerHour) + { + // Reset assignment. + ResumeOldAssignment( pSoldier ); + } + } + ubCounter++; + } +} + +BOOLEAN IsOutstandingFacilityDebtWithErrorReport() +{ + CHAR16 sString[256]; + SGPRect pCenteringRect= {0, 0, 640, INV_INTERFACE_START_Y }; + + if (giTotalOwedForFacilityOperationsToday && // Owe money + gfOutstandingFacilityDebt ) // Owed money tonight as well + { + if (LaptopSaveInfo.iCurrentBalance) + { + if (LaptopSaveInfo.iCurrentBalance >= giTotalOwedForFacilityOperationsToday) + { + // Do message "want to pay entire sum?" + swprintf( sString, New113HAMMessage[18], giTotalOwedForFacilityOperationsToday ); + if( guiCurrentScreen == MAP_SCREEN ) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayFacilityDebtManuallyYesNoBoxCallback ); + } + else + { + DoMessageBox( MSG_BOX_BASIC_STYLE, sString, GAME_SCREEN, MSG_BOX_FLAG_YESNO, PayFacilityDebtManuallyYesNoBoxCallback, &pCenteringRect ); + } + return FALSE; + } + else + { + // Do message "not enough money to pay entire debt" + swprintf( sString, New113HAMMessage[17], giTotalOwedForFacilityOperationsToday ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return FALSE; + } + } + else + { + // No money! + swprintf( sString, New113HAMMessage[16], giTotalOwedForFacilityOperationsToday ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + return FALSE; + } + } + + return TRUE; +} + +// HEADROCK HAM 3.6: Create the facility menu box. +void CreateFacilityAssignmentBox() +{ + FacilityAssignmentPosition.iX = OrigFacilityAssignmentPosition.iX; + + if( giBoxY != 0 ) + { + FacilityAssignmentPosition.iY = giBoxY + ( ASSIGN_MENU_FACILITY * GetFontHeight( MAP_SCREEN_FONT ) ); + } + + CreatePopUpBox(&ghFacilityAssignmentBox, FacilityAssignmentDimensions, FacilityAssignmentPosition, (POPUP_BOX_FLAG_CLIP_TEXT|POPUP_BOX_FLAG_CENTER_TEXT|POPUP_BOX_FLAG_RESIZE )); + SetBoxBuffer(ghFacilityAssignmentBox, FRAME_BUFFER); + SetBorderType(ghFacilityAssignmentBox,guiPOPUPBORDERS); + SetBackGroundSurface(ghFacilityAssignmentBox, guiPOPUPTEX); + SetMargins( ghFacilityAssignmentBox, 6, 6, 4, 4 ); + SetLineSpace(ghFacilityAssignmentBox, 2); + + // resize box to text + ResizeBoxToText( ghFacilityAssignmentBox ); + + DetermineBoxPositions( ); +} + +void CreateDestroyMouseRegionsForFacilityAssignmentMenu( void ) +{ + static BOOLEAN fCreated = FALSE; + + UINT32 uiMenuLine = 0; + UINT8 ubFacilityType = gubFacilityInSubmenu; + INT32 iFontHeight = 0; + INT32 iBoxXPosition = 0; + INT32 iBoxYPosition = 0; + SGPPoint pPosition; + INT32 iBoxWidth = 0; + SGPRect pDimensions; + SOLDIERTYPE *pSoldier = NULL; + INT8 bCurrentVehicleID = -1; + + INT32 iCounter = 0; + INT32 iCounterB = 0; + BOOLEAN fFoundVehicle = FALSE; + + + if( ( fShowFacilityAssignmentMenu == TRUE ) && ( fCreated == FALSE ) ) + { + if( ( fShowAssignmentMenu ) && ( guiCurrentScreen == MAP_SCREEN ) ) + { + SetBoxPosition( ghAssignmentBox, AssignmentPosition ); + } + + //HandleShadingOfLinesForFacilityAssignmentMenus( ); + CheckAndUpdateTacticalAssignmentPopUpPositions( ); + + // grab height of font + iFontHeight = GetLineSpace( ghFacilityAssignmentBox ) + GetFontHeight( GetBoxFont( ghFacilityAssignmentBox ) ); + + // get x.y position of box + GetBoxPosition( ghFacilityAssignmentBox, &pPosition); + + // grab box x and y position + iBoxXPosition = pPosition.iX; + iBoxYPosition = pPosition.iY; + + // get dimensions..mostly for width + GetBoxSize( ghFacilityAssignmentBox, &pDimensions ); + SetBoxSecondaryShade( ghFacilityAssignmentBox, FONT_YELLOW ); + + // get width + iBoxWidth = pDimensions.iRight; + + SetCurrentBox( ghFacilityAssignmentBox ); + + pSoldier = GetSelectedAssignSoldier( FALSE ); + + // define regions + //for( iCounter = 0; iCounter < GetNumberOfLinesOfTextInBox( ghFacilityBox ); iCounter++ ) + // run through list of staff/use facilities in sector and add them to pop up box + for ( iCounter = 0; iCounter < NUM_FACILITY_ASSIGNMENTS; iCounter++ ) + { + if ( gFacilityTypes[ ubFacilityType ].AssignmentData[iCounter].ubStaffLimit ) + { + if ( iCounter == FAC_REPAIR_VEHICLE ) + { + for ( iCounterB = 0; iCounterB < ubNumberOfVehicles; iCounterB++ ) + { + if ( pVehicleList[iCounterB].fValid == TRUE ) + { + if ( IsThisVehicleAccessibleToSoldier( pSoldier, iCounterB ) ) + { + MSYS_DefineRegion( &gFacilityAssignmentMenuRegion[ uiMenuLine ], ( INT16 )( iBoxXPosition ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * uiMenuLine ), ( INT16 )( iBoxXPosition + iBoxWidth ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * ( uiMenuLine + 1 ) ), MSYS_PRIORITY_HIGHEST - 2 , + MSYS_NO_CURSOR, FacilityAssignmentMenuMvtCallBack, FacilityAssignmentMenuBtnCallback ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 0, uiMenuLine ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 1, iCounter ); + // Store Vehicle ID in THIRD location + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 2, iCounterB ); + uiMenuLine++; + fFoundVehicle = TRUE; + } + } + } + if (!fFoundVehicle) + { + uiMenuLine++; // Skip this line, it'll always be shaded anyway. + } + } + else if ( iCounter == FAC_REPAIR_ROBOT ) + { + // is the ROBOT here? + if( IsRobotInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + // robot line only appears when it is around. + MSYS_DefineRegion( &gFacilityAssignmentMenuRegion[ uiMenuLine ], ( INT16 )( iBoxXPosition ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * uiMenuLine ), ( INT16 )( iBoxXPosition + iBoxWidth ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * ( uiMenuLine + 1 ) ), MSYS_PRIORITY_HIGHEST - 2 , + MSYS_NO_CURSOR, FacilityAssignmentMenuMvtCallBack, FacilityAssignmentMenuBtnCallback ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 0, uiMenuLine ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 1, iCounter ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 2, -1 ); + uiMenuLine++; + } + } + else + { + // add mouse region for each remaining assignment type + MSYS_DefineRegion( &gFacilityAssignmentMenuRegion[ uiMenuLine ], ( INT16 )( iBoxXPosition ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * uiMenuLine ), ( INT16 )( iBoxXPosition + iBoxWidth ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * ( uiMenuLine + 1 ) ), MSYS_PRIORITY_HIGHEST - 2 , + MSYS_NO_CURSOR, FacilityAssignmentMenuMvtCallBack, FacilityAssignmentMenuBtnCallback ); + + // Add tooltip for region + if (wcscmp(gFacilityTypes[ ubFacilityType ].AssignmentData[iCounter].szTooltipText, L"") != 0) + { + CHAR16 szTextLeft[300]; + CHAR16 szNewTextLeft[300]; + CHAR16 szTooltipText[500]; + wcscpy( szTextLeft, gFacilityTypes[ ubFacilityType ].AssignmentData[iCounter].szTooltipText ); + swprintf( szTooltipText, L"" ); + // Del First Part + BOOLEAN fLineSplit = TRUE; + + while (fLineSplit) + { + fLineSplit = WrapString( szTextLeft, szNewTextLeft, 250, FONT10ARIAL ); + wcscat( szTooltipText, szTextLeft ); + wcscat( szTooltipText, L"\n"); // Add line break. + wcscpy( szTextLeft, szNewTextLeft ); + } + + SetRegionFastHelpText( &gFacilityAssignmentMenuRegion[ uiMenuLine ], szTooltipText ); + } + //SetRegionHelpEndCallback( &gFacilityAssignmentMenuRegion[ uiMenuLine ], HelpTextDoneCallback ); + + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 0, uiMenuLine ); + // store assignment type ID in the SECOND user data + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 1, iCounter ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 2, -1 ); + uiMenuLine++; + } + } + } + + // cancel line + MSYS_DefineRegion( &gFacilityAssignmentMenuRegion[ uiMenuLine ], ( INT16 )( iBoxXPosition ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * uiMenuLine ), ( INT16 )( iBoxXPosition + iBoxWidth ), ( INT16 )( iBoxYPosition + GetTopMarginSize( ghAssignmentBox ) + ( iFontHeight ) * ( uiMenuLine + 1 ) ), MSYS_PRIORITY_HIGHEST - 2 , + MSYS_NO_CURSOR, FacilityAssignmentMenuMvtCallBack, FacilityAssignmentMenuBtnCallback ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 0, uiMenuLine ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 1, NUM_FACILITY_ASSIGNMENTS ); + MSYS_SetRegionUserData( &gFacilityAssignmentMenuRegion[ uiMenuLine ], 2, -1 ); + + // created + fCreated = TRUE; + + // pause game + PauseGame( ); + + // unhighlight all strings in box + UnHighLightBox( ghFacilityAssignmentBox ); + + fCreated = TRUE; + + //HandleShadingOfLinesForFacilityMenu( ); + } + else if( ( ( fShowFacilityAssignmentMenu == FALSE ) || ( fShowFacilityMenu == FALSE ) || ( fShowAssignmentMenu == FALSE ) ) && ( fCreated == TRUE ) ) + { + fCreated = FALSE; + + // remove these regions + for( uiMenuLine = 0; uiMenuLine < GetNumberOfLinesOfTextInBox( ghFacilityAssignmentBox ); uiMenuLine++ ) + { + MSYS_RemoveRegion( &gFacilityAssignmentMenuRegion[ uiMenuLine ] ); + } + + fShowFacilityAssignmentMenu = FALSE; + gfRenderPBInterface = TRUE; + + RestorePopUpBoxes( ); + fMapPanelDirty = TRUE; + fCharacterInfoPanelDirty= TRUE; + fTeamPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + HideBox( ghFacilityAssignmentBox ); + SetRenderFlags( RENDER_FLAG_FULL ); + + if ( fShowFacilityMenu ) + { + // remove highlight on the parent menu + UnHighLightBox( ghFacilityAssignmentBox ); + } + } + + return; +} + +void FacilityAssignmentMenuMvtCallBack ( MOUSE_REGION * pRegion, INT32 iReason ) +{ + // mvt callback handler for assignment region + INT32 iValue = -1; + + iValue = MSYS_GetRegionUserData( pRegion, 0 ); + + if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) + { + // highlight string + if( GetBoxShadeFlag( ghFacilityAssignmentBox, iValue ) == FALSE ) + { + // get the string line handle + HighLightBoxLine( ghFacilityAssignmentBox, iValue ); + } + } + else if (iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) + { + // unhighlight all strings in box + UnHighLightBox( ghFacilityAssignmentBox ); + } + return; +} + +void FacilityAssignmentMenuBtnCallback ( MOUSE_REGION * pRegion, INT32 iReason ) +{ + // btn callback handler for assignment region + INT16 ubFacilityType = -1; + INT16 ubAssignmentType = -1; + INT16 ubVehicleID = -1; + SOLDIERTYPE * pSoldier = NULL; + BOOLEAN fCanOperateFacility = TRUE; + + pSoldier = GetSelectedAssignSoldier( FALSE ); + + ubFacilityType = gubFacilityInSubmenu; + ubAssignmentType = (INT16)MSYS_GetRegionUserData( pRegion, 1 ); + ubVehicleID = (INT16)MSYS_GetRegionUserData( pRegion, 2 ); + + if (ubFacilityType <= 0 || ubFacilityType >= NUM_FACILITY_TYPES || ubAssignmentType <= 0) + { + return; + } + + if( ( iReason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) || ( iReason & MSYS_CALLBACK_REASON_RBUTTON_DWN ) ) + { + if ( (guiTacticalInterfaceFlags & INTERFACE_MAPSCREEN ) && !fShowMapInventoryPool ) + { + UnMarkButtonDirty( giMapBorderButtons[ MAP_BORDER_TOWN_BTN ] ); + } + } + + if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + if (ubAssignmentType > 0 && ubAssignmentType < NUM_FACILITY_ASSIGNMENTS ) + { + if (!BasicCanCharacterFacility( pSoldier )) + { + return; + } + + if (!CanCharacterFacilityWithErrorReport( pSoldier, (UINT8)ubFacilityType, (UINT8)ubAssignmentType ) ) + { + return; + } + + // Check for standing debt for Facility Operation. May trigger an on-screen prompt or error message. + if ( gFacilityTypes[ ubFacilityType ].AssignmentData[ ubAssignmentType ].sCostPerHour > 0 && // Facility costs money to operate + !IsOutstandingFacilityDebtWithErrorReport() ) // There's an outstanding debt for facilities + { + // Facility debt needs to be paid first. + return; + } + + // PASSED ALL TESTS - ALLOW SOLDIER TO WORK AT THIS FACILITY. + + fShowAssignmentMenu = FALSE; + giAssignHighLine = -1; + + pSoldier->bOldAssignment = pSoldier->bAssignment; // Set Old Assignment + + if( pSoldier->bOldAssignment == VEHICLE ) + { + TakeSoldierOutOfVehicle( pSoldier ); + } + RemoveCharacterFromSquads( pSoldier ); + + // Change assignment to new type + switch (ubAssignmentType) + { + case FAC_STAFF: + ChangeSoldiersAssignment( pSoldier, FACILITY_STAFF ); + break; + case FAC_REST: + ChangeSoldiersAssignment( pSoldier, FACILITY_REST ); + break; + case FAC_REPAIR_ITEMS: + MakeSureToolKitIsInHand( pSoldier ); + pSoldier->flags.fFixingRobot = FALSE; + pSoldier->bVehicleUnderRepairID = -1; + ChangeSoldiersAssignment( pSoldier, REPAIR ); + break; + case FAC_REPAIR_VEHICLE: + MakeSureToolKitIsInHand( pSoldier ); + pSoldier->flags.fFixingRobot = FALSE; + pSoldier->bVehicleUnderRepairID = (INT8)ubVehicleID; + ChangeSoldiersAssignment( pSoldier, REPAIR ); + break; + case FAC_REPAIR_ROBOT: + MakeSureToolKitIsInHand( pSoldier ); + pSoldier->flags.fFixingRobot = TRUE; + pSoldier->bVehicleUnderRepairID = -1; + ChangeSoldiersAssignment( pSoldier, REPAIR ); + break; + case FAC_DOCTOR: + MakeSureMedKitIsInHand( pSoldier ); + ChangeSoldiersAssignment( pSoldier, DOCTOR ); + break; + case FAC_PATIENT: + ChangeSoldiersAssignment( pSoldier, PATIENT ); + break; + case FAC_PRACTICE_STRENGTH: + pSoldier->bTrainStat = STRENGTH; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_DEXTERITY: + pSoldier->bTrainStat = DEXTERITY; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_AGILITY: + pSoldier->bTrainStat = AGILITY; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_HEALTH: + pSoldier->bTrainStat = HEALTH; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_MARKSMANSHIP: + pSoldier->bTrainStat = MARKSMANSHIP; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_LEADERSHIP: + pSoldier->bTrainStat = LEADERSHIP; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_MEDICAL: + pSoldier->bTrainStat = MEDICAL; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_MECHANICAL: + pSoldier->bTrainStat = MECHANICAL; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_PRACTICE_EXPLOSIVES: + pSoldier->bTrainStat = EXPLOSIVE_ASSIGN; + ChangeSoldiersAssignment( pSoldier, TRAIN_SELF ); + break; + case FAC_STUDENT_STRENGTH: + pSoldier->bTrainStat = STRENGTH; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_DEXTERITY: + pSoldier->bTrainStat = DEXTERITY; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_AGILITY: + pSoldier->bTrainStat = AGILITY; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_HEALTH: + pSoldier->bTrainStat = HEALTH; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_MARKSMANSHIP: + pSoldier->bTrainStat = MARKSMANSHIP; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_LEADERSHIP: + pSoldier->bTrainStat = LEADERSHIP; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_MEDICAL: + pSoldier->bTrainStat = MEDICAL; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_MECHANICAL: + pSoldier->bTrainStat = MECHANICAL; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_STUDENT_EXPLOSIVES: + pSoldier->bTrainStat = EXPLOSIVE_ASSIGN; + ChangeSoldiersAssignment( pSoldier, TRAIN_BY_OTHER ); + break; + case FAC_TRAINER_STRENGTH: + pSoldier->bTrainStat = STRENGTH; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_DEXTERITY: + pSoldier->bTrainStat = DEXTERITY; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_AGILITY: + pSoldier->bTrainStat = AGILITY; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_HEALTH: + pSoldier->bTrainStat = HEALTH; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_MARKSMANSHIP: + pSoldier->bTrainStat = MARKSMANSHIP; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_LEADERSHIP: + pSoldier->bTrainStat = LEADERSHIP; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_MEDICAL: + pSoldier->bTrainStat = MEDICAL; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_MECHANICAL: + pSoldier->bTrainStat = MECHANICAL; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + case FAC_TRAINER_EXPLOSIVES: + pSoldier->bTrainStat = EXPLOSIVE_ASSIGN; + ChangeSoldiersAssignment( pSoldier, TRAIN_TEAMMATE ); + break; + + } + + AssignMercToAMovementGroup( pSoldier ); + MakeSoldiersTacticalAnimationReflectAssignment( pSoldier ); + + pSoldier->sFacilityTypeOperated = ubFacilityType; // Set soldier as working at this facility. + + if( gFacilityTypes[ ubFacilityType ].AssignmentData[ ubAssignmentType ].sCostPerHour > 0 ) + { + // Ask player if he wishes to expend money on operating the facility. + HandleInterfaceMessageForCostOfOperatingFacility( pSoldier, (UINT8)ubAssignmentType ); + } + gfRenderPBInterface = TRUE; + } + else + { + // stop showing menu + fShowFacilityAssignmentMenu = FALSE; + + // unhighlight the assignment box + UnHighLightBox( ghFacilityBox ); + + // reset list + ResetSelectedListForMapScreen( ); + gfRenderPBInterface = TRUE; + } + // rerender tactical stuff + gfRenderPBInterface = TRUE; + + fTeamPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; + } + +} + +BOOLEAN HandleAssignmentExpansionAndHighLightForFacilityMenu( void ) +{ + if( fShowFacilityAssignmentMenu ) + { + HighLightBoxLine( ghFacilityBox, gubFacilityLineForSubmenu ); + return( TRUE ); + } + + return( FALSE ); +} + +void HandleShadingOfLinesForFacilityAssignmentMenu( void ) +{ + SOLDIERTYPE *pSoldier = NULL; + INT32 iCounter = 0; + INT32 iCounterB = 0; + INT32 iNumLine = 0; + UINT8 ubFacilityType = gubFacilityInSubmenu; + BOOLEAN fFoundVehicle = FALSE; + + if( ( fShowFacilityAssignmentMenu == FALSE ) || ( ghFacilityAssignmentBox == -1 ) ) + { + return; + } + + pSoldier = GetSelectedAssignSoldier( FALSE ); + + // PLEASE NOTE: make sure any changes you do here are reflected in all 3 routines which must remain in synch: + // CreateDestroyMouseRegionForRepairMenu(), DisplayRepairMenu(), and HandleShadingOfLinesForRepairMenu(). + + // run through all possible assignments. Shade as necessary + for ( iCounter = 0; iCounter < NUM_FACILITY_ASSIGNMENTS; iCounter++ ) + { + if ( gFacilityTypes[ubFacilityType].AssignmentData[iCounter].ubStaffLimit ) + { + if ( iCounter == FAC_REPAIR_VEHICLE ) + { + // Test to see whether there are any. + for ( iCounterB = 0; iCounterB < ubNumberOfVehicles; iCounterB++ ) + { + if ( pVehicleList[iCounterB].fValid == TRUE ) + { + if ( IsThisVehicleAccessibleToSoldier( pSoldier, iCounterB ) && + CanCharacterFacility( pSoldier, ubFacilityType, iCounter ) ) + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + } + else + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + SecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + } + iNumLine++; + fFoundVehicle = TRUE; + } + } + if (fFoundVehicle = FALSE) + { + // The line here says "Repair Vehicle", and is shaded because there's no vehicle present. + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + ShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + iNumLine++; + } + } + else if ( iCounter == FAC_REPAIR_ROBOT ) + { + // is the ROBOT here? + if( IsRobotInThisSector( pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ ) ) + { + if ( CanCharacterFacility( pSoldier, ubFacilityType, iCounter ) ) + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + } + else + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + SecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + } + iNumLine++; + } + else + { + // Line won't appear in the menu, so don't bother shading it. + } + } + else if ( iCounter == FAC_PATIENT ) + { + // is injured? + if ( CanCharacterPatient ( pSoldier ) ) + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + } + else + { + // A fully shaded line appears. + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + ShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + } + iNumLine++; + } + else if ( !BasicCanCharacterFacility( pSoldier ) ) + { + // Character cannot perform facility work at all. This shouldn't happen, actually. + // Every line in this menu is, in theory, either valid or "not valid right now"... + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + ShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + iNumLine++; + } + else if ( !CanCharacterFacility( pSoldier, ubFacilityType, iCounter ) ) + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + SecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + iNumLine++; + } + else + { + UnShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + UnSecondaryShadeStringInBox( ghFacilityAssignmentBox, iNumLine ); + iNumLine++; + } + } + } + return; +} \ No newline at end of file diff --git a/Strategic/Assignments.h b/Strategic/Assignments.h index 617c3bca..66a15049 100644 --- a/Strategic/Assignments.h +++ b/Strategic/Assignments.h @@ -26,7 +26,7 @@ #define VEHICLE_REPAIR_POINTS_DIVISOR 10 - +// HEADROCK HAM 3.6: Added separate Mobile Militia training assignment. // Assignments Defines enum{ SQUAD_1 =0, @@ -57,8 +57,11 @@ enum{ REPAIR, TRAIN_SELF, TRAIN_TOWN, + TRAIN_MOBILE, // HEADROCK HAM 3.6: Training mobile militia. TRAIN_TEAMMATE, TRAIN_BY_OTHER, + FACILITY_STAFF, // HEADROCK HAM 3.6: Operating a facility for strategic gain. + FACILITY_REST, // HEADROCK HAM 3.6: Facility equivalent of resting (no assignment) ASSIGNMENT_DEAD, ASSIGNMENT_UNCONCIOUS, // unused ASSIGNMENT_POW, @@ -83,6 +86,16 @@ enum{ // NOTE: Wisdom isn't trainable! }; +// HEADROCK HAM 3.2: Set bitwise flags for locating facilities in current sector (for training/otherwise) +// facilities flags +// HEADROCK HAM 3.4: Removed due to facility externalization. +/*#define AT_HOSPITAL 0x00000001 +#define AT_INDUSTRY 0x00000002 +#define AT_PRISON 0x00000004 +#define AT_MILITARY 0x00000008 +#define AT_AIRPORT 0x00000010 +#define AT_GUN_RANGE 0x00000020*/ + typedef struct TOWN_TRAINER_TYPE { @@ -106,6 +119,7 @@ BOOLEAN CanCharacterPatient( SOLDIERTYPE *pCharacter ); // can character train militia? BOOLEAN CanCharacterTrainMilitia( SOLDIERTYPE *pCharacter ); +BOOLEAN CanCharacterTrainMobileMilitia( SOLDIERTYPE *pSoldier ); // can character train stat?..as train self or as trainer? BOOLEAN CanCharacterTrainStat( SOLDIERTYPE *pSoldier, INT8 bStat, BOOLEAN fTrainSelf, BOOLEAN fTrainTeammate ); @@ -116,6 +130,9 @@ BOOLEAN CanCharacterPractise( SOLDIERTYPE *pCharacter ); // can this character train others? BOOLEAN CanCharacterTrainTeammates( SOLDIERTYPE *pSoldier ); +// HEADROCK HAM 3.6: Can character use a specific facility? +BOOLEAN CanCharacterFacility( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType, UINT8 ubAssignmentType ); + // put character on duty? BOOLEAN CanCharacterOnDuty( SOLDIERTYPE *pCharacter ); @@ -183,16 +200,18 @@ UINT8 CalculateRepairPointsForRepairman(SOLDIERTYPE *pSoldier, UINT16 *pusMaxPts // get bonus tarining pts due to an instructor for this student -INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE *pStudent, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16 *pusMaxPts ); +// HEADROCK HAM 3.5: Three functions below have lost an argument which is no longer required ("uiAtGunRange", which was "uiAtFacility" in HAM 3.4) +INT16 GetBonusTrainingPtsDueToInstructor( SOLDIERTYPE *pInstructor, SOLDIERTYPE *pStudent, INT8 bTrainStat, UINT16 *pusMaxPts ); // get training pts for this soldier -INT16 GetSoldierTrainingPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16 *pusMaxPts ); +INT16 GetSoldierTrainingPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, UINT16 *pusMaxPts ); // pts for being a student for this soldier -INT16 GetSoldierStudentPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, BOOLEAN fAtGunRange, UINT16 *pusMaxPts ); +INT16 GetSoldierStudentPts( SOLDIERTYPE *pSoldier, INT8 bTrainStat, UINT16 *pusMaxPts ); +// HEADROCK HAM 3.6: Completely unused. // reset these soldiers -void ResetAssignmentsForAllSoldiersInSectorWhoAreTrainingTown( SOLDIERTYPE *pSoldier ); +//void ResetAssignmentsForAllSoldiersInSectorWhoAreTrainingTown( SOLDIERTYPE *pSoldier ); // Handle assignment done void AssignmentDone( SOLDIERTYPE *pSoldier, BOOLEAN fSayQuote, BOOLEAN fMeToo ); @@ -207,6 +226,9 @@ extern INT32 ghAttributeBox; extern INT32 ghRemoveMercAssignBox; extern INT32 ghContractBox; extern INT32 ghMoveBox; +// HEADROCK HAM 3.6: Facility Menu, Submenu +extern INT32 ghFacilityBox; +extern INT32 ghFacilityAssignmentBox; //extern INT32 ghUpdateBox; @@ -219,6 +241,9 @@ extern MOUSE_REGION gSquadMenuRegion[ ]; extern MOUSE_REGION gContractMenuRegion[ ]; extern MOUSE_REGION gRemoveMercAssignRegion[ ]; extern MOUSE_REGION gVehicleMenuRegion[]; +// HEADROCK HAM 3.6: Facility Menu. +extern MOUSE_REGION gFacilityMenuRegion[]; +extern MOUSE_REGION gFacilityMenuRegion[]; extern BOOLEAN fShownContractMenu; extern BOOLEAN fShownAssignmentMenu; @@ -260,6 +285,16 @@ void CreateDestroyMouseRegionForRepairMenu( void ); void RepairMenuMvtCallback(MOUSE_REGION * pRegion, INT32 iReason ); void RepairMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ); +// HEADROCK HAM 3.6: Facility Menu +void CreateDestroyMouseRegionForFacilityMenu( void ); +void FacilityMenuMvtCallback(MOUSE_REGION * pRegion, INT32 iReason ); +void FacilityMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ); + +// HEADROCK HAM 3.6: Facility sub-menu +void CreateDestroyMouseRegionsForFacilityAssignmentMenu( void ); +void FacilityAssignmentMenuMvtCallBack( MOUSE_REGION * pRegion, INT32 iReason ); +void FacilityAssignmentMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ); + // contract menu void CreateDestroyMouseRegionsForContractMenu( void ); void ContractMenuBtnCallback( MOUSE_REGION * pRegion, INT32 iReason ); @@ -334,6 +369,20 @@ void HandleTrainingWakeSynchronize( SOLDIERTYPE *pSoldier ); BOOLEAN FindAnyAwakeTrainers( SOLDIERTYPE *pTrainee ); BOOLEAN FindAnyAwakeTrainees( SOLDIERTYPE *pTrainer ); +// HEADROCK HAM 3.6: Functions for testing and reporting reasons why character can't train here. +BOOLEAN CanCharacterTrainMilitiaWithErrorReport( SOLDIERTYPE *pSoldier ); +BOOLEAN CanCharacterTrainMobileMilitiaWithErrorReport( SOLDIERTYPE *pSoldier ); +// HEADROCK HAM 3.6: Function for testing and reporting reasons why character can't staff a facility. +BOOLEAN CanCharacterFacilityWithErrorReport( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType, UINT8 ubAssignmentType ); +void HandleInterfaceMessageForCostOfOperatingFacility( SOLDIERTYPE *pSoldier, UINT8 ubAssignmentType ); +void PayFacilityCostsYesNoBoxCallback( UINT8 bExitValue ); +void FacilityStaffingRejected( void ); +void ResetAllExpensiveFacilityAssignments(); +BOOLEAN IsOutstandingFacilityDebtWithErrorReport(); +extern INT8 gubFacilityInSubmenu; +extern UINT8 gubFacilityLineForSubmenu; +extern SOLDIERTYPE *gpFacilityStaffer; + #endif diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index c59f961e..a0512dfa 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -339,7 +339,8 @@ void RenderSoldierCellHealth( SOLDIERCELL *pCell ); void RenderSoldierCell( SOLDIERCELL *pCell ); void RenderSoldierCellBars( SOLDIERCELL *pCell ); -void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ); +// HEADROCK HAM 3.4: New argument configuration to make use of revamped movement controls +void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fForTraining, BOOLEAN fForBattle ); //Dynamic globals -- to conserve memory, all global variables are allocated upon entry @@ -2146,8 +2147,8 @@ void CreateAutoResolveInterface() ARCreateMilitiaSquad( &cnt, ubEliteMilitia, ubRegMilitia, ubGreenMilitia, gpAR->ubSectorX, gpAR->ubSectorY); // Add the militia in the surrounding sectors - GenerateDirectionInfos( gpAR->ubSectorX, gpAR->ubSectorY, &uiDirNumber, pMoveDir, - ( GetTownIdForSector( gpAR->ubSectorX, gpAR->ubSectorY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, FALSE ); + // HEADROCK HAM 3.4: Changed argument configuration. + GenerateDirectionInfos( gpAR->ubSectorX, gpAR->ubSectorY, &uiDirNumber, pMoveDir, FALSE, TRUE ); for( i=0; i= 0 && gFacilityLocations[cnt].uiSectorID <= 256); cnt++ ) + { + // Is the current sector valid? Is the facility set to be revealed on game-start? + if (gFacilityLocations[cnt].uiSectorID > 0 && gFacilityLocations[cnt].uiSectorID < 257 && + gFacilityLocations[cnt].fHidden == 0) + { + // Reveal the facility. + pSector = &SectorInfo[gFacilityLocations[cnt].uiSectorID]; + pSector->uiFacilitiesFlags |= (1 << (gFacilityLocations[cnt].uiFacilityType-1)); + } + }*/ + + /* HEADROCK HAM 3.4: Externalized. // Cambria hospital pSector = &SectorInfo[SEC_G8]; pSector->uiFacilitiesFlags |= SFCF_HOSPITAL; @@ -304,6 +324,7 @@ void InitKnowFacilitiesFlags( ) pSector->uiFacilitiesFlags |= SFCF_AIRPORT; pSector = &SectorInfo[SEC_O4]; pSector->uiFacilitiesFlags |= SFCF_AIRPORT; + */ return; } @@ -603,13 +624,16 @@ void InitNewCampaign() InitWorld(); // Lesh: generate different world each time using alternative maps InitMiningLocations(); - InitKnowFacilitiesFlags( ); + + // HEADROCK HAM 3.5: This is no longer required. + //InitKnowFacilitiesFlags( ); BuildUndergroundSectorInfoList(); if (!is_networked) // allow overhead view of omerta A9 on game onset - SetSectorFlag( startingX, startingY, startingZ, SF_ALREADY_VISITED ); //hayden + // HEADROCK HAM 3.5: Externalized. + SetSectorFlag( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ, SF_ALREADY_VISITED ); //hayden //Generates the initial forces in a new campaign. The idea is to randomize numbers and sectors //so that no two games are the same. diff --git a/Strategic/Campaign Types.h b/Strategic/Campaign Types.h index af142e7f..3d8491d1 100644 --- a/Strategic/Campaign Types.h +++ b/Strategic/Campaign Types.h @@ -112,19 +112,214 @@ enum MAX_MILITIA_LEVELS }; -// facilities flags +// HEADROCK HAM 3.5: No longer needed. Facility types externalized. +/*// facilities flags #define SFCF_HOSPITAL 0x00000001 #define SFCF_INDUSTRY 0x00000002 #define SFCF_PRISON 0x00000004 #define SFCF_MILITARY 0x00000008 #define SFCF_AIRPORT 0x00000010 -#define SFCF_GUN_RANGE 0x00000020 +#define SFCF_GUN_RANGE 0x00000020*/ -// coordinates of shooting range sector +// HEADROCK HAM 3.4: These are no longer needed anywhere in the code. +/*// coordinates of shooting range sector #define GUN_RANGE_X 13 #define GUN_RANGE_Y MAP_ROW_H #define GUN_RANGE_Z 0 +// HEADROCK HAM 3.2: New values for the location of an Industrial sector in Grumm H2. This sector boosts repair rates. +#define INDUSTRIAL_X 2 +#define INDUSTRIAL_Y MAP_ROW_H +#define INDUSTRIAL_Z 0*/ + +// HEADROCK HAM 3.4: Facility Locations +typedef struct FACILITYLOCATIONS +{ + BOOLEAN fFacilityHere; + UINT8 ubHidden; +}FACILITYLOCATIONS; + +enum +{ + // Enumerate the different assignments that a facility can emulate. + FAC_AMBIENT = 0, + FAC_STAFF, + FAC_REST, + FAC_REPAIR_ITEMS, + FAC_REPAIR_VEHICLE, + FAC_REPAIR_ROBOT, + FAC_DOCTOR, + FAC_PATIENT, + FAC_PRACTICE_STRENGTH, + FAC_PRACTICE_DEXTERITY, + FAC_PRACTICE_AGILITY, + FAC_PRACTICE_HEALTH, + FAC_PRACTICE_MARKSMANSHIP, + FAC_PRACTICE_MEDICAL, + FAC_PRACTICE_MECHANICAL, + FAC_PRACTICE_LEADERSHIP, + FAC_PRACTICE_EXPLOSIVES, + FAC_STUDENT_STRENGTH, + FAC_STUDENT_DEXTERITY, + FAC_STUDENT_AGILITY, + FAC_STUDENT_HEALTH, + FAC_STUDENT_MARKSMANSHIP, + FAC_STUDENT_MEDICAL, + FAC_STUDENT_MECHANICAL, + FAC_STUDENT_LEADERSHIP, + FAC_STUDENT_EXPLOSIVES, + FAC_TRAINER_STRENGTH, + FAC_TRAINER_DEXTERITY, + FAC_TRAINER_AGILITY, + FAC_TRAINER_HEALTH, + FAC_TRAINER_MARKSMANSHIP, + FAC_TRAINER_MEDICAL, + FAC_TRAINER_MECHANICAL, + FAC_TRAINER_LEADERSHIP, + FAC_TRAINER_EXPLOSIVES, + NUM_FACILITY_ASSIGNMENTS, +}; + + +// Enumerated list of risk types +enum +{ + RISK_STRENGTH = 0, + RISK_HEALTH, + RISK_AGILITY, + RISK_DEXTERITY, + RISK_WISDOM, + RISK_MARKSMANSHIP, + RISK_LEADERSHIP, + RISK_EXPLOSIVES, + RISK_MECHANICAL, + RISK_MEDICAL, + + RISK_INJURY, + RISK_MORALE, + RISK_FATIGUE, + RISK_DRUNK, + + RISK_LOYALTY_LOCAL, + RISK_LOYALTY_GLOBAL, + + NUM_RISKS, +}; + +typedef struct FACILITYRISKTYPE +{ + // The risks involved with perfoming an assignment at a specific facility. + + UINT8 ubChance; // Chance out of X that this risk will trigger every hour. X is INI-defined and is normally 1000. + INT8 bBaseEffect; // Base result. If negative, result will always be negative. If positive, result will always be positive. + // If 0, result can be either negative or positive. + UINT8 ubRange; // Range of deviation for the base effect. + +} FACILITYRISKTYPE; + +typedef struct FACILITYASSIGNMENTTYPE +{ + // This contains data about performing a specific assignment at a specific facility. + + UINT8 ubStaffLimit; // Maximum number of people who can perform this assignment simultaneously + CHAR16 szTooltipText[300]; // Tooltip explaining the assignment in general terms. + INT16 sCostPerHour; // Cost per hour. Can be negative to indicate FLAT PROFIT per hour. + + UINT16 usPerformance; // Percentage effectiveness of this assignment compared to non-emulated assignment (100 = normal) + UINT16 usFatigue; // Percentage speed of losing Max Breath (100 = normal) + UINT16 usSleep; // Percentage effectiveness of sleeping on the job (100 = normal) + UINT16 usKitDegrade; // Percentage speed of kit degrade (100 = normal) + + UINT8 ubMaximumMorale; // Maximum morale achievable while performing this assignment + UINT8 ubMaximumBreath; // Maximum breath achievable while performing this assignment + + UINT8 ubDetectEnemiesImmediate; // Detection of enemies in adjacent sectors, value = range of detection. + BOOLEAN fDetectEnemiesLongrange; // Detection of enemies in explored territory + BOOLEAN fDetectEnemiesDynamic; // Detection of enemies in Mobile Militia areas + BOOLEAN fDetectEnemiesAnywhere; // Detection of enemies all over the map + BOOLEAN fCountEnemiesInWild; // Count number of enemies in wilderness sectors + BOOLEAN fCountEnemiesInCities; // Count number of enemies in city sectors + + INT16 sSkyriderCostModifier; // Flat modifier for Skyrider's Cost Per Tile + UINT16 usMineIncomeModifier; // Percentage income adjustment + BOOLEAN fOnlyLocalMineAffected; // Determines whether income modifier applies to local mine only, or all mines. + + UINT8 ubMinimumStrength; // Minimum STR Requirement to begin this assignment + UINT8 ubMinimumHealth; // Minimum HLT Requirement to begin this assignment + UINT8 ubMinimumWisdom; // Minimum WIS Requirement to begin this assignment + UINT8 ubMinimumAgility; // Minimum AGI Requirement to begin this assignment + UINT8 ubMinimumDexterity; // Minimum DEX Requirement to begin this assignment + UINT8 ubMinimumMarksmanship; // Minimum MRK Requirement to begin this assignment + UINT8 ubMinimumLeadership; // Minimum LDR Requirement to begin this assignment + UINT8 ubMinimumExplosives; // Minimum EXP Requirement to begin this assignment + UINT8 ubMinimumMechanical; // Minimum MEC Requirement to begin this assignment + UINT8 ubMinimumMedical; // Minimum MED Requirement to begin this assignment + UINT8 ubMinimumLevel; // Minimum LVL Requirement to begin this assignment + + UINT8 ubMinimumLoyaltyHere; // Minimum loyalty required in the town where the facility is placed (if any) + UINT8 ubMinimumBreath; // Minimum Breath Requirement to begin this assignment + UINT8 ubMinimumMorale; // Minimum Morale Requirement to begin this assignment + + FACILITYRISKTYPE Risk[ NUM_RISKS ]; // Data about risks/chances possible when doing this assignment + +} FACILITYASSIGNMENTTYPE; + +// HEADROCK HAM 3.5: Facility Type +typedef struct FACILITYTYPE +{ + // Basic data about the facility + + CHAR16 szFacilityName[30]; // Long facility name + CHAR16 szFacilityShortName[15]; // Short facility name + UINT8 ubTotalStaffLimit; // Total number of people who can work here simultaneously + + UINT8 ubMilitiaTrainersAllowed; // Number of Militia trainers allowed simultaneously + UINT8 ubMobileMilitiaTrainersAllowed; // Number of Mobile Militia trainers allowed simultaneously + UINT16 usMilitiaTraining; // Percentage effectiveness of Militia training (100 = normal) + UINT16 usMobileMilitiaTraining; // Percentage effectiveness of Mobile Militia training (100 = normal) + + FACILITYASSIGNMENTTYPE AssignmentData[ NUM_FACILITY_ASSIGNMENTS ]; // Data about possible assignments that can be done here + +} FACILITYTYPE; + + +// HEADROCK HAM 3.5: Maximum number of different facility types +#define MAX_NUM_FACILITY_TYPES 255 +// HEADROCK HAM 3.5: Number of facilities placed on the map +extern UINT16 NUM_FACILITIES; +// HEADROCK HAM 3.5: Number of facility types read from XML +extern UINT16 NUM_FACILITY_TYPES; +// HEADROCK HAM 3.4: Array to hold facilities in each sector +extern FACILITYLOCATIONS gFacilityLocations[256][MAX_NUM_FACILITY_TYPES]; +// HEADROCK HAM 3.5: Array to hold all different facility types +extern FACILITYTYPE gFacilityTypes[MAX_NUM_FACILITY_TYPES]; + +// HEADROCK HAM 3.6: Bloodcat Placement and Encounter data. +typedef struct BLOODCATPLACEMENT +{ + UINT8 PlacementType; + UINT8 ubMinBloodcats; + UINT8 ubMaxBloodcats; + UINT8 ubAmbushChance; + BOOLEAN ubFactionAffiliation; + BOOLEAN fRespawn; +}BLOODCATPLACEMENT; + +// HEADROCK HAM 3.6: Array to hold bloodcat placements and user-set bloodcat encounter data. There are 256 of these +// (one for each sector), with 4 sublevels (one for each difficulty) +extern BLOODCATPLACEMENT gBloodcatPlacements[256][4]; +// Location of the Bloodcat Lair. There can be only one, as there's a quest that relies on its location. This is a +// sectorID, 0-255. +extern UINT8 gubBloodcatLairSectorId; + +// HEADROCK HAM 3.6: Enumerated Bloodcat Placement types +enum +{ + BLOODCAT_PLACEMENT_AMBUSH = 0, + BLOODCAT_PLACEMENT_STATIC, + BLOODCAT_PLACEMENT_LAIR, + NUM_BLOODCAT_PLACEMENT_TYPES +}; //Vehicle types #define FOOT 0x01 //anywhere @@ -227,8 +422,8 @@ typedef struct SECTORINFO //throught the sector without entering it. INT8 bNameId; INT8 bUSUSED; - INT8 bBloodCats; - INT8 bBloodCatPlacements; + INT8 bBloodCats; // Actual number of bloodcats existing on this map. + INT8 bBloodCatPlacements; // "Maximum" potential bloodcats that can appear on this map. INT8 UNUSEDbSAMCondition; UINT8 ubTravelRating; //Represents how travelled a sector is. Typically, the higher the travel rating, @@ -236,15 +431,22 @@ typedef struct SECTORINFO //around. This value is used for determining how often items would "vanish" from //a sector (nice theory, except it isn't being used that way. Stealing is only in towns. ARM) UINT8 ubNumberOfCivsAtLevel[ MAX_MILITIA_LEVELS ]; // town militia per experience class, 0/1/2 is GREEN/REGULAR/ELITE - UINT16 usUNUSEDMilitiaLevels; // unused (ARM) - UINT8 ubUNUSEDNumberOfJoeBlowCivilians; // unused (ARM) + // HEADROCK HAM 3.6: Adding separate training percentage for MOBILES. + UINT8 ubMobileMilitiaTrainingPercentDone; + UINT8 ubMobileMilitiaTrainingHundredths; + BOOLEAN fMobileMilitiaTrainingPaid; + // Replacing these variables with the ones above. They really are unused. + //UINT16 usUNUSEDMilitiaLevels; // unused (ARM) + //UINT8 ubUNUSEDNumberOfJoeBlowCivilians; // unused (ARM) UINT32 uiTimeCurrentSectorWasLastLoaded; //Specifies the last time the player was in the sector UINT8 ubUNUSEDNumberOfEnemiesThoughtToBeHere; // using bLastKnownEnemies instead UINT32 uiTimeLastPlayerLiberated; //in game seconds (used to prevent the queen from attacking for awhile) BOOLEAN fSurfaceWasEverPlayerControlled; - UINT8 bFiller1; + // HEADROCK HAM 3.6: Flag to determine whether enemy units in this sector can be seen, and whether their numbers + // should be displayed. One filler was replaced to make room. + UINT8 ubDetectionLevel; UINT8 bFiller2; UINT8 bFiller3; diff --git a/Strategic/Facilities.cpp b/Strategic/Facilities.cpp new file mode 100644 index 00000000..08fea50b --- /dev/null +++ b/Strategic/Facilities.cpp @@ -0,0 +1,1733 @@ +////////////////////////////////////////////////////////////////// +// HEADROCK HAM 3.5: Facilities +// +// This file contains functions that test facility data (read +// from XML) whenever necessary. See XML_Facilities.cpp and +// XML_FacilityTypes.cpp for the reading XML functions. +////////////////////////////////////////////////////////////////// + +#include "builddefines.h" + +#ifdef PRECOMPILEDHEADERS + #include "Strategic All.h" + #include "GameSettings.h" +#else + #include "Campaign Types.h" + #include "Facilities.h" + #include "Soldier Control.h" + #include "Map Screen Interface.h" + #include "Overhead.h" + #include "Assignments.h" + #include "GameSettings.h" + #include "LaptopSave.h" + #include "finances.h" + #include "Text.h" + #include "Strategic Town Loyalty.h" + #include "Strategic Mines.h" + #include "SkillCheck.h" + #include "Random.h" + #include "strategicmap.h" + #include "Game Clock.h" + #include "Campaign.h" + #include "Drugs And Alcohol.cpp" + #include "Soldier Control.cpp" + #include "MilitiaSquads.h" + #include "Tactical Save.h" +#endif + +INT16 gsSkyriderCostModifier; +// HEADROCK HAM 3.6: Strategic info variable, total of costs accumulated for the use of facilities today. Deducted +// from account and reset at midnight, unless player can't pay (in which case, carries over to the next day). +// This variable is SAVED and LOADED. +INT32 giTotalOwedForFacilityOperationsToday = 0; +BOOLEAN gfOutstandingFacilityDebt = TRUE; + +// This function returns a specific modifier out of the FacilityTypes array. Modifiers exist inside an assignment +// sub-array, so the final result depends on the assignment the soldier is performing. The difference between +// this and simply reading data from the facility array is that in this case, "AMBIENT" effects are also included, +// which take effect regardless of the assignment. +INT16 GetFacilityModifier( UINT8 ubModifierType, UINT8 ubFacilityType, UINT8 ubAssignmentType ) +{ + INT16 sAssignmentModifier; + INT16 sAmbientModifier; + + // Are we checking an actual assignment at a specific facility? + if (ubAssignmentType != FAC_AMBIENT && + ubFacilityType > 0 ) + { + switch (ubModifierType) + { + case FACILITY_PERFORMANCE_MOD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].usPerformance; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usPerformance; + sAssignmentModifier = 100 + ((sAssignmentModifier-100) + (sAmbientModifier-100)); + return (sAssignmentModifier); + + case FACILITY_FATIGUE_MOD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].usFatigue; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usFatigue; + sAssignmentModifier = 100 + ((sAssignmentModifier-100) + (sAmbientModifier-100)); + return (sAssignmentModifier); + + case FACILITY_SLEEP_MOD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].usSleep; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usSleep; + sAssignmentModifier = 100 + ((sAssignmentModifier-100) + (sAmbientModifier-100)); + return (sAssignmentModifier); + + case FACILITY_KIT_DEGRADE_MOD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].usKitDegrade; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usKitDegrade; + sAssignmentModifier = 100 + ((sAssignmentModifier-100) + (sAmbientModifier-100)); + return (sAssignmentModifier); + + case FACILITY_MAX_MORALE: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMaximumMorale; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].ubMaximumMorale; + sAssignmentModifier = __min(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_MAX_BREATH: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubMaximumBreath; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].ubMaximumBreath; + sAssignmentModifier = __min(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_DETECT_IMMEDIATE: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].ubDetectEnemiesImmediate; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].ubDetectEnemiesImmediate; + sAssignmentModifier = __max(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_DETECT_DYNAMIC: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].fDetectEnemiesDynamic; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fDetectEnemiesDynamic; + sAssignmentModifier = __max(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_DETECT_LONGRANGE: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].fDetectEnemiesLongrange; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fDetectEnemiesLongrange; + sAssignmentModifier = __max(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_DETECT_ANYWHERE: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].fDetectEnemiesAnywhere; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fDetectEnemiesAnywhere; + sAssignmentModifier = __max(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_COUNT_INWILD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].fCountEnemiesInWild; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fCountEnemiesInWild; + sAssignmentModifier = __max(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_COUNT_INCITIES: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].fCountEnemiesInCities; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fCountEnemiesInCities; + sAssignmentModifier = __max(sAssignmentModifier, sAmbientModifier); + return (sAssignmentModifier); + + case FACILITY_SKYRIDER_COST_MOD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sSkyriderCostModifier; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].sSkyriderCostModifier; + sAssignmentModifier = sAssignmentModifier + sAmbientModifier; + return (sAssignmentModifier); + + case FACILITY_MINE_INCOME_MOD: + sAssignmentModifier = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].usMineIncomeModifier; + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usMineIncomeModifier; + sAssignmentModifier = 100 + ((sAssignmentModifier-100) + (sAmbientModifier-100)); + return (sAssignmentModifier); + + default: + return (0); + } + } + // Only ambient effect please + else + { + switch (ubModifierType) + { + case FACILITY_PERFORMANCE_MOD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usPerformance; + return (sAmbientModifier); + + case FACILITY_FATIGUE_MOD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usFatigue; + return (sAmbientModifier); + + case FACILITY_SLEEP_MOD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usSleep; + return (sAmbientModifier); + + case FACILITY_KIT_DEGRADE_MOD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usKitDegrade; + return (sAmbientModifier); + + case FACILITY_MAX_MORALE: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].ubMaximumMorale; + return (sAmbientModifier); + + case FACILITY_MAX_BREATH: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].ubMaximumBreath; + return (sAmbientModifier); + + case FACILITY_DETECT_IMMEDIATE: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].ubDetectEnemiesImmediate; + return (sAmbientModifier); + + case FACILITY_DETECT_DYNAMIC: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fDetectEnemiesDynamic; + return (sAmbientModifier); + + case FACILITY_DETECT_LONGRANGE: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fDetectEnemiesLongrange; + return (sAmbientModifier); + + case FACILITY_DETECT_ANYWHERE: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fDetectEnemiesAnywhere; + return (sAmbientModifier); + + case FACILITY_COUNT_INWILD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fCountEnemiesInWild; + return (sAmbientModifier); + + case FACILITY_COUNT_INCITIES: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].fCountEnemiesInCities; + return (sAmbientModifier); + + case FACILITY_SKYRIDER_COST_MOD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].sSkyriderCostModifier; + return (sAmbientModifier); + + case FACILITY_MINE_INCOME_MOD: + sAmbientModifier = gFacilityTypes[ubFacilityType].AssignmentData[0].usMineIncomeModifier; + return (sAmbientModifier); + + default: + return (0); + } + } +} + +// This is a handler function that runs through every facility in the soldier's sector, retrieving one type of +// modifier from each facility. +INT16 GetSectorModifier( SOLDIERTYPE *pSoldier, UINT8 ubModifierType ) +{ + INT16 sCurrent; + INT16 Result; + UINT8 ubSectorID = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + INT8 bAssignmentType = GetSoldierFacilityAssignmentIndex(pSoldier); + + // Set "default" value. + switch (ubModifierType) + { + case FACILITY_PERFORMANCE_MOD: + case FACILITY_FATIGUE_MOD: + case FACILITY_SLEEP_MOD: + case FACILITY_KIT_DEGRADE_MOD: + case FACILITY_MINE_INCOME_MOD: + Result = 100; + break; + + case FACILITY_MAX_MORALE: + case FACILITY_MAX_BREATH: + Result = 100; + break; + + case FACILITY_DETECT_IMMEDIATE: + case FACILITY_DETECT_DYNAMIC: + case FACILITY_DETECT_LONGRANGE: + case FACILITY_DETECT_ANYWHERE: + case FACILITY_COUNT_INWILD: + case FACILITY_COUNT_INCITIES: + Result = 0; + break; + + case FACILITY_SKYRIDER_COST_MOD: + Result = 0; + break; + } + + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + if (gFacilityLocations[ubSectorID][cnt].fFacilityHere) + { + if ( bAssignmentType != -1) + { + // Soldier has a facility-based assignment. Is he working this type of facility? + if ((UINT8)pSoldier->sFacilityTypeOperated == (UINT8)cnt) + { + sCurrent = GetFacilityModifier( ubModifierType, (UINT8)cnt, (UINT8)bAssignmentType ); + } + else // He's operating another type of facility. Get only the ambient effect. + { + sCurrent = GetFacilityModifier( ubModifierType, (UINT8)cnt, FAC_AMBIENT ); + } + } + else + { + // Soldier is not doing facility-specific work. Get only the ambient effect. + sCurrent = GetFacilityModifier( ubModifierType, (UINT8)cnt, FAC_AMBIENT ); + } + + // Calculate overall result so far. Calculation method is based on the modifier we're looking for. + switch (ubModifierType) + { + case FACILITY_PERFORMANCE_MOD: + case FACILITY_FATIGUE_MOD: + case FACILITY_SLEEP_MOD: + case FACILITY_KIT_DEGRADE_MOD: + case FACILITY_MINE_INCOME_MOD: + Result = 100 + ((100-Result) + (100-sCurrent)); + break; + + case FACILITY_MAX_MORALE: + case FACILITY_MAX_BREATH: + Result = __min(Result, sCurrent); + break; + + case FACILITY_DETECT_IMMEDIATE: + case FACILITY_DETECT_DYNAMIC: + case FACILITY_DETECT_LONGRANGE: + case FACILITY_DETECT_ANYWHERE: + case FACILITY_COUNT_INWILD: + case FACILITY_COUNT_INCITIES: + Result = __max(Result, sCurrent); + break; + + case FACILITY_SKYRIDER_COST_MOD: + Result += sCurrent; + break; + } + } + } + + return (Result); +} + +////////////////////////////////////////////////// +// This function reads the status of intelligence gathering currently being done by your mercs. They work at special +// facilities that can enable enemy detection in certain sectors. +// +// There are several detection methods, and they allow/disallow detection in different groups of sectors. +// DETECT_ENEMIES_IMMEDIATE: Detection in sectors near to where the merc is working. +// DETECT_ENEMIES_DYNAMIC: Detection in sectors where Mobile Militia are allowed to roam. +// DETECT_ENEMIES_LONGRANGE: Detection in all explored sectors. +// DETECT_ENEMIES_ANYWHERE: Detect enemy units all over the map. +// Also there are two Counting methods, which allow reading the exact number of troops in specific sectors. +// COUNT_ENEMIES_IN_WILD: Can count enemy numbers in non-city sectors only. +// COUNT_ENEMIES_IN_CITIES: Can counter enemy numbers in city sectors only. +// +// This function runs through each working merc, and flags all the appropriate sectors. Later on, the program reads +// these flags when it wants to find out whether an enemy unit is visible on the map, and whether the exact number of +// enemies in that unit is also shown. +void UpdateStrategicDetectionLevel( ) +{ + SOLDIERTYPE * pSoldier; + UINT8 ubCounter = 0; + + // Reset the detection flag in SectorInfo for all sectors. + for (UINT16 X = 0; X < 256; X++) + { + // Reset sector detection flags. + SectorInfo[X].ubDetectionLevel = 0; + } + + // Flag to hold the current detection level before calculating its effects on the detection mask. + UINT8 ubStrategicDetectionLevel = 0; + + // Run through list of player-characters + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character truly valid? + if( !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && + pSoldier->bSectorZ == 0 && + pSoldier->stats.bLife >= OKLIFE && + !(pSoldier->flags.fMercAsleep) ) + { + UINT8 ubSector = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + + if (GetSoldierFacilityAssignmentIndex( pSoldier ) == -1) + { + // Skip this soldier, he is not performing a facility assignment. + ubCounter++; + continue; + } + + UINT8 ubAssignmentType = (UINT8)GetSoldierFacilityAssignmentIndex( pSoldier ); + + // Test to see whether the character is properly set up at this facility. + if ( CanCharacterFacility( pSoldier, ubFacilityType, ubAssignmentType ) && + GetWorldTotalMin() - pSoldier->uiLastAssignmentChangeMin >= (UINT32)gGameExternalOptions.ubMinutesForAssignmentToCount ) + { + // Make sure facility type is valid. + Assert(ubFacilityType < MAX_NUM_FACILITY_TYPES); + + if (!(gFacilityLocations[ubSector][ubFacilityType].fFacilityHere)) + { + // Somehow, the character is working in a nonexistant facility. Odd. Skip him. + ubCounter++; + continue; + } + + ///////////////////////////////////////////////////////// + // Begin testing for facility detection bonuses. + + // Does game allow Dynamic Roaming Militia? + if (gGameExternalOptions.fDynamicRestrictRoaming) + { + // Does facility allow Dynamic Detection? + if (GetFacilityModifier(FACILITY_DETECT_DYNAMIC, ubFacilityType, ubAssignmentType)) + { + ubStrategicDetectionLevel |= (1 << DETECT_ENEMIES_DYNAMIC); + } + } + + // Does facility allow Long-Range Detection? + if (GetFacilityModifier(FACILITY_DETECT_LONGRANGE, ubFacilityType, ubAssignmentType)) + { + ubStrategicDetectionLevel |= (1 << DETECT_ENEMIES_LONGRANGE); + } + + // Does facility allow Omniscient Detection? + if (GetFacilityModifier(FACILITY_DETECT_ANYWHERE, ubFacilityType, ubAssignmentType)) + { + ubStrategicDetectionLevel |= (1 << DETECT_ENEMIES_ANYWHERE); + } + + // Does facility allow Counting Enemies in the Wilderness? + if (GetFacilityModifier(FACILITY_COUNT_INWILD, ubFacilityType, ubAssignmentType)) + { + ubStrategicDetectionLevel |= (1 << COUNT_ENEMIES_IN_WILD); + } + + // Does facility allow Counting Enemies in the Wilderness? + if (GetFacilityModifier(FACILITY_COUNT_INCITIES, ubFacilityType, ubAssignmentType)) + { + ubStrategicDetectionLevel |= (1 << COUNT_ENEMIES_IN_CITIES); + } + + // "Immediate" detection allows spotting enemies within a certain radius of the facility. + UINT16 usImmediateDetectionRange = (UINT16)GetFacilityModifier(FACILITY_DETECT_IMMEDIATE, ubFacilityType, ubAssignmentType); + if (usImmediateDetectionRange) + { + // Loop through all nearby sectors. + UINT8 sMapX; + UINT8 sMapY; + for (sMapX = pSoldier->sSectorX - usImmediateDetectionRange; sMapX <= pSoldier->sSectorX + usImmediateDetectionRange; sMapX++) + { + for (sMapY = pSoldier->sSectorY - usImmediateDetectionRange; sMapY <= pSoldier->sSectorY + usImmediateDetectionRange; sMapY++) + { + if (sMapX <= 0 || sMapX >= MAP_WORLD_X - 1 || + sMapY <= 0 || sMapY >= MAP_WORLD_Y - 1 ) + { + // Sector out of bounds. + continue; + } + + if (abs(pSoldier->sSectorX - sMapX) <= usImmediateDetectionRange && + abs(pSoldier->sSectorY - sMapY) <= usImmediateDetectionRange) + { + // Sector within range. Update its detection flag. + SectorInfo[SECTOR(sMapX, sMapY)].ubDetectionLevel |= 1; + } + } + } + } + } + } + ubCounter++; + } + // Some flags have been set. Run through all sectors and apply this. + for (UINT16 X = 0; X < 256; X++) + { + // Reset detection level first + SectorInfo[X].ubDetectionLevel = 0; + + if (ubStrategicDetectionLevel & (1<flags.uiStatusFlags & SOLDIER_VEHICLE ) && + pSoldier->bSectorZ == 0 && + pSoldier->stats.bLife >= OKLIFE && + !(pSoldier->flags.fMercAsleep) ) + { + UINT8 ubSector = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + if (GetSoldierFacilityAssignmentIndex( pSoldier ) == -1) + { + // Skip this soldier, he is not performing a facility assignment. + ubCounter++; + continue; + } + UINT8 ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + + // Is character using a facility? + if ( CanCharacterFacility( pSoldier, ubFacilityType, ubAssignmentType ) && + GetWorldTotalMin() - pSoldier->uiLastAssignmentChangeMin >= (UINT32)gGameExternalOptions.ubMinutesForAssignmentToCount ) + { + + // Make sure facility type is valid. + Assert(ubFacilityType < MAX_NUM_FACILITY_TYPES); + + if (!(gFacilityLocations[ubSector][ubFacilityType].fFacilityHere)) + { + // Somehow, the character is working in a nonexistant facility. Odd. Skip him. + ubCounter++; + continue; + } + + // Does facility change Skyrider Costs at all? + gsSkyriderCostModifier += GetFacilityModifier( FACILITY_SKYRIDER_COST_MOD, ubFacilityType, ubAssignmentType ); + } + } + ubCounter++; + } +} + +// HEADROCK HAM 3.6: Goes through all characters "STAFFING" a facility, and checks to see whether they alter any +// strategic variables. +void UpdateGlobalVariablesFromFacilities( void ) +{ + UpdateStrategicDetectionLevel( ); + + UpdateSkyriderCostModifier( ); + + UpdateFacilityUsageCosts( ); +} + +// This function runs hourly. It calculates the total cost of all facilities being operated currently, +// and adds them to a global "debt" variable that tracks expenditure. At the end of the day, the debt is deducted from +// the player's account. +void UpdateFacilityUsageCosts( ) +{ + SOLDIERTYPE * pSoldier; + UINT8 ubCounter = 0; + INT16 sCost = 0; + + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character truly valid? + if( !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && + pSoldier->bSectorZ == 0 && + pSoldier->stats.bLife >= OKLIFE && + !(pSoldier->flags.fMercAsleep) ) + { + UINT8 ubSector = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + if (GetSoldierFacilityAssignmentIndex( pSoldier ) == -1) + { + // Skip this soldier, he is not performing a facility assignment. + ubCounter++; + continue; + } + UINT8 ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + + // Is character using a facility? + if ( CanCharacterFacility( pSoldier, ubFacilityType, ubAssignmentType ) && + GetWorldTotalMin() - pSoldier->uiLastAssignmentChangeMin >= (UINT32)gGameExternalOptions.ubMinutesForAssignmentToCount ) + { + // Make sure facility type is valid. + Assert(ubFacilityType < MAX_NUM_FACILITY_TYPES); + + if (!(gFacilityLocations[ubSector][ubFacilityType].fFacilityHere)) + { + // Somehow, the character is working in a nonexistant facility. Odd. Skip him. + ubCounter++; + continue; + } + + sCost = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour; + + if (sCost > 0) + { + ///////////////////////////////////////////////////////// + // Increase debt for operating this facility + + giTotalOwedForFacilityOperationsToday += gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour; + } + else if (sCost < 0) + { + ///////////////////////////////////////////////////////// + // Put money directly into the player's account + + LaptopSaveInfo.iCurrentBalance += (-1 * sCost); + } + + } + } + ubCounter++; + } +} + +// HEADROCK HAM 3.6: This function runs once at the end of each day. +// It handles debt accrued by operation facilities. If there is enough money to pay the debt off, this +// is done automatically. If only part of the money is available, or none of it, your account is emptied and all +// "expensive" facility work ends immediately. You also suffer a loyalty hit across Arulco based on how much money +// you owe. Facility work cannot continue before the debt is paid off! +void HandleDailyPaymentFacilityDebt( void ) +{ + CHAR16 sString[256]; + INT32 iToPay = 0; + + if (LaptopSaveInfo.iCurrentBalance >= giTotalOwedForFacilityOperationsToday) + { + // Pay total debt. + iToPay = giTotalOwedForFacilityOperationsToday; + gfOutstandingFacilityDebt = FALSE; + } + else if (LaptopSaveInfo.iCurrentBalance < giTotalOwedForFacilityOperationsToday) + { + if (LaptopSaveInfo.iCurrentBalance > 0) + { + // Still have some money in the bank. Pay it. + iToPay = LaptopSaveInfo.iCurrentBalance; + swprintf( sString, New113HAMMessage[14], iToPay, giTotalOwedForFacilityOperationsToday - iToPay ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + HandleFacilityDebtLoyaltyHit(); + ResetAllExpensiveFacilityAssignments(); + gfOutstandingFacilityDebt = TRUE; + } + else if (LaptopSaveInfo.iCurrentBalance <= 0) + { + // No money to pay! + iToPay = 0; + swprintf( sString, New113HAMMessage[15], giTotalOwedForFacilityOperationsToday); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + HandleFacilityDebtLoyaltyHit(); + ResetAllExpensiveFacilityAssignments(); + gfOutstandingFacilityDebt = TRUE; + } + } + + if (iToPay > 0) + { + AddTransactionToPlayersBook( FACILITY_OPERATIONS, 0, GetWorldTotalMin(), -( iToPay ) ); + giTotalOwedForFacilityOperationsToday -= iToPay; + } +} + +// HEADROCK HAM 3.6: This functionruns a player-requested payoff of facility debt. It is only possible if the player +// has enough money in his account to pay off the ENTIRE debt. The function is called when a player tries to assign +// a merc to facility duty while an outstanding debt exists. Debts are only outstanding if they failed to be paid off +// during the previous midnight. +void HandleManualPaymentFacilityDebt( void ) +{ + INT32 iToPay = 0; + + // Is there any debt at all? + if (giTotalOwedForFacilityOperationsToday > 0) + { + // This function should ONLY be called if there's enough money in the bank to pay off the entire debt. + Assert( LaptopSaveInfo.iCurrentBalance >= giTotalOwedForFacilityOperationsToday ); + + // Pay total debt. + iToPay = giTotalOwedForFacilityOperationsToday; + AddTransactionToPlayersBook( FACILITY_OPERATIONS, 0, GetWorldTotalMin(), -( iToPay ) ); + giTotalOwedForFacilityOperationsToday = 0; + gfOutstandingFacilityDebt = FALSE; + } +} + +// HEADROCK HAM 3.6: Apply income bonuses from Facility Work to a specific mine. +INT32 MineIncomeModifierFromFacility( UINT8 ubMine ) +{ + Assert (ubMine > 0 && ubMine < MAX_NUMBER_OF_MINES); + + SOLDIERTYPE *pSoldier; + INT32 iModifier = 0; + + UINT8 ubCounter = 0; + while (gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character truly valid? + if( !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && + pSoldier->bSectorZ == 0 && + pSoldier->stats.bLife >= OKLIFE && + !(pSoldier->flags.fMercAsleep) ) + { + UINT8 ubSector = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + if (GetSoldierFacilityAssignmentIndex( pSoldier ) == -1) + { + // Skip this soldier, he is not performing a facility assignment. + ubCounter++; + continue; + } + UINT8 ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + + // Is character using a facility? + if ( CanCharacterFacility( pSoldier, ubFacilityType, ubAssignmentType ) && + GetWorldTotalMin() - pSoldier->uiLastAssignmentChangeMin >= (UINT32)gGameExternalOptions.ubMinutesForAssignmentToCount ) + { + if (GetFacilityModifier( FACILITY_MINE_INCOME_MOD, ubFacilityType, ubAssignmentType ) && // Facility adjusts mine income + (gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].fOnlyLocalMineAffected == 0 || // All mines affected + GetMineIndexForSector( pSoldier->sSectorX, pSoldier->sSectorY ) == ubMine ) ) // This mine is here + { + iModifier += (gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].usMineIncomeModifier - 100); + } + } + } + ubCounter++; + } + + return iModifier; +} + +// This function converts a soldier's current assignment into a single Facility Assignment Type Index. +// The Assignment Type Index is used for referencing various data and effects associated with performing a +// SPECIFIC assignment with the help of a SPECIFIC facility. It's basically a reference number for the gFacilityTypes +// array. +INT8 GetSoldierFacilityAssignmentIndex( SOLDIERTYPE *pSoldier ) +{ + // Read soldier's assignment + INT8 bAssignment = pSoldier->bAssignment; + INT8 bAssignmentIndex = -1; // Return variable. -1 = Non-facility assignment. + + if (bAssignment <= ON_DUTY || + bAssignment == PATIENT || + bAssignment == VEHICLE || + bAssignment == IN_TRANSIT || + bAssignment == TRAIN_TOWN || + bAssignment == TRAIN_MOBILE || + bAssignment > FACILITY_REST ) + { + // Soldier is performing a distinctly NON-FACILITY assignment. + return (-1); + } + + if (pSoldier->sFacilityTypeOperated == -1) + { + // Soldier is not set to work at a facility... + return (-1); + } + + switch (bAssignment) + { + case DOCTOR: + bAssignmentIndex = FAC_DOCTOR; + break; + case PATIENT: + bAssignmentIndex = FAC_PATIENT; + break; + case REPAIR: + // Determine which kind of repair is he performing + if ( pSoldier->bVehicleUnderRepairID != -1 ) + { + bAssignmentIndex = FAC_REPAIR_VEHICLE; + } + else if ( pSoldier->flags.fFixingRobot ) + { + bAssignmentIndex = FAC_REPAIR_ROBOT; + } + else + { + bAssignmentIndex = FAC_REPAIR_ITEMS; + } + break; + case TRAIN_SELF: + switch (pSoldier->bTrainStat) + { + case STRENGTH: + bAssignmentIndex = FAC_PRACTICE_STRENGTH; + break; + case DEXTERITY: + bAssignmentIndex = FAC_PRACTICE_DEXTERITY; + break; + case AGILITY: + bAssignmentIndex = FAC_PRACTICE_AGILITY; + break; + case HEALTH: + bAssignmentIndex = FAC_PRACTICE_HEALTH; + break; + case MARKSMANSHIP: + bAssignmentIndex = FAC_PRACTICE_MARKSMANSHIP; + break; + case MEDICAL: + bAssignmentIndex = FAC_PRACTICE_MEDICAL; + break; + case MECHANICAL: + bAssignmentIndex = FAC_PRACTICE_MECHANICAL; + break; + case LEADERSHIP: + bAssignmentIndex = FAC_PRACTICE_LEADERSHIP; + break; + case EXPLOSIVE_ASSIGN: + bAssignmentIndex = FAC_PRACTICE_EXPLOSIVES; + break; + } + break; + case TRAIN_TEAMMATE: + switch (pSoldier->bTrainStat) + { + case STRENGTH: + bAssignmentIndex = FAC_TRAINER_STRENGTH; + break; + case DEXTERITY: + bAssignmentIndex = FAC_TRAINER_DEXTERITY; + break; + case AGILITY: + bAssignmentIndex = FAC_TRAINER_AGILITY; + break; + case HEALTH: + bAssignmentIndex = FAC_TRAINER_HEALTH; + break; + case MARKSMANSHIP: + bAssignmentIndex = FAC_TRAINER_MARKSMANSHIP; + break; + case MEDICAL: + bAssignmentIndex = FAC_TRAINER_MEDICAL; + break; + case MECHANICAL: + bAssignmentIndex = FAC_TRAINER_MECHANICAL; + break; + case LEADERSHIP: + bAssignmentIndex = FAC_TRAINER_LEADERSHIP; + break; + case EXPLOSIVE_ASSIGN: + bAssignmentIndex = FAC_TRAINER_EXPLOSIVES; + break; + } + break; + case TRAIN_BY_OTHER: + switch (pSoldier->bTrainStat) + { + case STRENGTH: + bAssignmentIndex = FAC_STUDENT_STRENGTH; + break; + case DEXTERITY: + bAssignmentIndex = FAC_STUDENT_DEXTERITY; + break; + case AGILITY: + bAssignmentIndex = FAC_STUDENT_AGILITY; + break; + case HEALTH: + bAssignmentIndex = FAC_STUDENT_HEALTH; + break; + case MARKSMANSHIP: + bAssignmentIndex = FAC_STUDENT_MARKSMANSHIP; + break; + case MEDICAL: + bAssignmentIndex = FAC_STUDENT_MEDICAL; + break; + case MECHANICAL: + bAssignmentIndex = FAC_STUDENT_MECHANICAL; + break; + case LEADERSHIP: + bAssignmentIndex = FAC_STUDENT_LEADERSHIP; + break; + case EXPLOSIVE_ASSIGN: + bAssignmentIndex = FAC_STUDENT_EXPLOSIVES; + break; + } + break; + case FACILITY_STAFF: + bAssignmentIndex = FAC_STAFF; + break; + case FACILITY_REST: + bAssignmentIndex = FAC_REST; + break; + default: + bAssignmentIndex = -1; + } + + return (bAssignmentIndex); + +} + +// This function runs a specific kind of RISK associated with a facility. The character's stats may affect the result. +INT16 FacilityRiskResult( SOLDIERTYPE *pSoldier, UINT8 ubRiskType, UINT8 ubFacilityType, UINT8 ubAssignmentType ) +{ + INT16 Result = 0; + + UINT8 ubChance; + INT8 bBaseEffect; + UINT8 ubRange; + + // Read risk data directly from facility array. + ubChance = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].Risk[ubRiskType].ubChance; + bBaseEffect = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].Risk[ubRiskType].bBaseEffect; + ubRange = gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].Risk[ubRiskType].ubRange; + + // For now, only these variables can effect risk outcome. In the future there may be more effects, like traits + // and personality, or even sex. + UINT8 ubStrength = EffectiveStrength( pSoldier ); + UINT8 ubAgility = EffectiveAgility( pSoldier ); + UINT8 ubDexterity = EffectiveDexterity( pSoldier ); + UINT8 ubHealth = __min(pSoldier->stats.bLife, pSoldier->stats.bLifeMax); + UINT8 ubWisdom = EffectiveWisdom( pSoldier ); + UINT8 ubLeadership = EffectiveLeadership( pSoldier ); + UINT8 ubExplosives = EffectiveExplosive( pSoldier ); + UINT8 ubExpLevel = EffectiveExpLevel( pSoldier ); + UINT8 ubLocalLoyalty = 0; + if (GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ) != BLANK_SECTOR ) + { + ubLocalLoyalty = gTownLoyalty[ GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ) ].ubRating; + } + else + { + ubLocalLoyalty = 0; + } + + /////////////// + INT8 bAbsoluteMaxResult; + INT8 bAbsoluteMinResult; + + // Calculate absolute maximum variance. The result can never be outside these. + if (bBaseEffect < 0) + { + bAbsoluteMaxResult = __min((bBaseEffect + ubRange), -1); + bAbsoluteMinResult = bBaseEffect - ubRange; + } + else if (bBaseEffect > 0) + { + bAbsoluteMaxResult = bBaseEffect + ubRange; + bAbsoluteMinResult = __max(1, (bBaseEffect - ubRange)); + } + else + { + bAbsoluteMaxResult = bBaseEffect + ubRange; + bAbsoluteMinResult = bBaseEffect - ubRange; + } + + INT8 bCombinedStats; + + // Begin calculating the effect of skills on increasing/reducing final result. Use a mix of stats to reach a range + // of 0 to 100. + + switch (ubRiskType) + { + case RISK_STRENGTH: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubAgility * 0.3) + (ubExpLevel * 5)); + break; + case RISK_DEXTERITY: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.4) + (ubExpLevel * 6)); + break; + case RISK_AGILITY: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubExpLevel * 8)); + break; + case RISK_HEALTH: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.5) + (ubExpLevel * 5)); + break; + case RISK_WISDOM: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.6) + (ubExpLevel * 4)); + break; + case RISK_MARKSMANSHIP: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubAgility * 0.4) + (ubExpLevel * 4)); + break; + case RISK_MEDICAL: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.3) + (ubDexterity * 0.4) + (ubExpLevel * 3)); + break; + case RISK_MECHANICAL: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.3) + (ubDexterity * 0.5) + (ubExpLevel * 2)); + break; + case RISK_LEADERSHIP: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.4) + (ubLeadership * 0.3) + (ubExpLevel * 3)); + break; + case RISK_EXPLOSIVES: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubAgility * 0.3) + (ubDexterity * 0.3) + (ubExpLevel * 2)); + break; + case RISK_INJURY: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubDexterity * 0.2) + (ubAgility * 0.4) + (ubExpLevel * 2)); + break; + case RISK_MORALE: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubLeadership * 0.4) + (ubExpLevel * 4)); + break; + case RISK_FATIGUE: + bCombinedStats = (INT8)__min(100, (ubAgility * 0.1) + (ubStrength * 0.3) + (ubHealth * 0.2) + (ubExpLevel * 5)); + break; + case RISK_DRUNK: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.5) + (ubHealth * 0.3) + (ubExpLevel * 2)); + break; + case RISK_LOYALTY_LOCAL: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.2) + (ubLeadership * 0.3) + (ubStrength * 0.1) + (ubExpLevel * 2) + (ubLocalLoyalty * 0.2) ); + break; + case RISK_LOYALTY_GLOBAL: + bCombinedStats = (INT8)__min(100, (ubWisdom * 0.3) + (ubLeadership * 0.3) + (ubExpLevel * 2) + (ubLocalLoyalty * 0.2)); + break; + } + + if (bCombinedStats == 100) + { + // Character is as skilled as possible. Automatic "best" result. + return (bAbsoluteMaxResult); + } + if (bCombinedStats == 0) + { + // Character is completely inept? Strange. Automatic "worst" result. + return (bAbsoluteMinResult); + } + + // we now have a range of 0 to 100. Let's apply an externalized modifier of 0 to -100. + bCombinedStats -= gGameExternalOptions.ubFacilityDangerRate; + + // The chance of triggering an event is also based on stats. + if (bBaseEffect > 0 ) + { + // Chance increases with good stats, decreased with bad stats. + ubChance = ubChance + ((bCombinedStats * ubChance) / 100 ); + } + else if (bBaseEffect < 0 ) + { + // Chance decreases with good stats, increases with bad stats. + ubChance = ubChance - ((bCombinedStats * ubChance) / 100 ); + } + else + { + // Chance is unaffected + } + + // Alright, let's see if the event is triggered. + if (PreRandom(gGameExternalOptions.usFacilityEventRarity)>ubChance) + { + // Chance failed to trigger. + return (0); + } + else + { + // Let's find out how bad/good the result is. + + // By now, the Combined Stats variable is anywhere between -100 and +100. + // We use this to move the Base Effect point from its original location, effectively giving us a better + // or worse result based on our stats. + bBaseEffect += (bCombinedStats * (ubRange+1)) / 100; + bBaseEffect = __min(bBaseEffect, bAbsoluteMaxResult); + bBaseEffect = __max(bBaseEffect, bAbsoluteMinResult); + + // The deviation is also affected by our stats. + ubRange -= abs((bCombinedStats * (ubRange+1)) / 100); // If the Base Effect has moved any, then the range shrinks accordingly. + + // Effectively what happens is that skilled characters will get better results more consistently, and avoid + // bad results consistently as well. + // The opposite is also true - characters with insufficient skills tend to get worse results consistently, + // and rarely (if ever) get the best results possible. + + //The final result is: + Result = bBaseEffect + ((PreRandom((ubRange*2)+1)) - ubRange); + + // Naturally, the character can't ever breach the range of possible results as defined by the facility data. + Result = __min(Result, bAbsoluteMaxResult); + Result = __max(Result, bAbsoluteMinResult); + + return (Result); + } +} + +void HandleHourlyRisks() +{ + INT32 iCounter = 0; + SOLDIERTYPE *pSoldier; + + + while (gCharactersList[iCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ iCounter ].usSolID ]; + + // Is character truly valid? + if( !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && + pSoldier->bSectorZ == 0 && + pSoldier->stats.bLife >= OKLIFE && + !(pSoldier->flags.fMercAsleep) ) + { + // Run this character's risks. + HandleRisksForSoldier( pSoldier ); + } + iCounter++; + } +} + +void HandleRisksForSoldier( SOLDIERTYPE *pSoldier ) +{ + INT32 iCounter = 0; + INT32 iCounterB = 0; + UINT8 ubFacilityType = 0; + UINT8 ubAssignmentType = 0; + + if (pSoldier->sFacilityTypeOperated != -1) + { + // Soldier is working at a specific facility. Risks associated with this specific facility and assignment + // have priority. + ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + HandleRisksForSoldierFacilityAssignment( pSoldier, ubFacilityType, ubAssignmentType ); + } + + // Test for triggers related to any AMBIENT facility in the same sector. + for (iCounterB; iCounterB < NUM_FACILITY_TYPES; iCounterB++) + { + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][iCounterB].fFacilityHere) + { + // Test this facility for ambient effects. + HandleRisksForSoldierFacilityAssignment( pSoldier, iCounterB, FAC_AMBIENT ); + } + } +} + +void HandleRisksForSoldierFacilityAssignment( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType, UINT8 ubAssignmentType ) +{ + INT32 iCounter = 0; + INT32 Result = 0; + INT32 iNewValue = 0; + + CHAR16 sString[256]; + CHAR16 szSectorGrid[10]; + SGPRect pCenteringRect= {0, 0, 640, INV_INTERFACE_START_Y }; + + UINT8 ubTownID = 0; + BOOLEAN fOperatingFacility = (GetSoldierFacilityAssignmentIndex( pSoldier ) == -1) ? FALSE : TRUE; + + BOOLEAN fBadResult = FALSE; + + UINT16 usColor = FONT_MCOLOR_LTRED; + + for (iCounter; iCounter < NUM_RISKS; iCounter++) + { + fBadResult = FALSE; + + if (gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].Risk[iCounter].ubChance > 0) + { + if (iCounter == RISK_LOYALTY_LOCAL) + { + ubTownID = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + if (ubTownID == BLANK_SECTOR) + { + // No town here, so this risk doesn't have any effect anyway... + continue; + } + } + + Result = FacilityRiskResult( pSoldier, iCounter, ubFacilityType, ubAssignmentType); + if (Result != 0) + { + switch (iCounter) + { + case RISK_STRENGTH: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usStrengthSubpointsToImprove; + StatChange( pSoldier, STRAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bStrength = __max(1,pSoldier->stats.bStrength + Result); + gMercProfiles[ pSoldier->ubProfile ].bStrength = pSoldier->stats.bStrength; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeStrengthTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( STRENGTH_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, STRAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[0], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[0], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_AGILITY: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usAgilitySubpointsToImprove; + StatChange( pSoldier, AGILAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bAgility = __max(1,pSoldier->stats.bAgility + Result); + gMercProfiles[ pSoldier->ubProfile ].bAgility = pSoldier->stats.bAgility; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeAgilityTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( AGIL_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, AGILAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[1], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[1], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_DEXTERITY: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usDexteritySubpointsToImprove; + StatChange( pSoldier, DEXTAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bDexterity = __max(1,pSoldier->stats.bDexterity + Result); + gMercProfiles[ pSoldier->ubProfile ].bDexterity = pSoldier->stats.bDexterity; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeDexterityTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( DEX_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, DEXTAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[2], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[2], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_WISDOM: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usWisdomSubpointsToImprove; + StatChange( pSoldier, WISDOMAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bWisdom = __max(1,pSoldier->stats.bWisdom + Result); + gMercProfiles[ pSoldier->ubProfile ].bWisdom = pSoldier->stats.bWisdom; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeWisdomTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( WIS_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, WISDOMAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[3], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[3], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_HEALTH: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usHealthSubpointsToImprove; + StatChange( pSoldier, HEALTHAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bLifeMax = __max(1,pSoldier->stats.bLifeMax + Result); + gMercProfiles[ pSoldier->ubProfile ].bLifeMax = pSoldier->stats.bLifeMax; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeHealthTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( HEALTH_INCREASE ); + pSoldier->stats.bLife += Result; + if (pSoldier->stats.bLife < OKLIFE) + { + HandleTakeDamageDeath( pSoldier, 0, TAKE_DAMAGE_BLOODLOSS ); + } + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, HEALTHAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + if (pSoldier->stats.bLife >= OKLIFE) + { + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[4], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[4], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + else if(pSoldier->stats.bLife < OKLIFE && pSoldier->stats.bLife > 0) + { + // Soldier's health is driven very low. Automatically removed from duty. + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[28], pSoldier->name, szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[29], pSoldier->name, gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + AddCharacterToAnySquad( pSoldier ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + } + break; + case RISK_MARKSMANSHIP: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usMarksmanshipSubpointsToImprove; + StatChange( pSoldier, MARKAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bMarksmanship = __max(1,pSoldier->stats.bMarksmanship + Result); + gMercProfiles[ pSoldier->ubProfile ].bMarksmanship = pSoldier->stats.bMarksmanship; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeMarksmanshipTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( MRK_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, MARKAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[5], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[5], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_LEADERSHIP: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usLeadershipSubpointsToImprove; + StatChange( pSoldier, LDRAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bLeadership = __max(1,pSoldier->stats.bLeadership + Result); + gMercProfiles[ pSoldier->ubProfile ].bLeadership = pSoldier->stats.bLeadership; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeLeadershipTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( LDR_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, LDRAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[6], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[6], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_MECHANICAL: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usMechanicalSubpointsToImprove; + StatChange( pSoldier, MECHANAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bMechanical = __max(1,pSoldier->stats.bMechanical + Result); + gMercProfiles[ pSoldier->ubProfile ].bMechanical = pSoldier->stats.bMechanical; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeMechanicalTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( MECH_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, MECHANAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[7], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[7], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_MEDICAL: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usMedicalSubpointsToImprove; + StatChange( pSoldier, MEDICALAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bMedical = __max(1,pSoldier->stats.bMedical + Result); + gMercProfiles[ pSoldier->ubProfile ].bMedical = pSoldier->stats.bMedical; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeMedicalTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( MED_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, MEDICALAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[8], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[8], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_EXPLOSIVES: + if (Result > 0) + { + // Soldier doesn't gain stat points directly, instead he gets a number of chances + // equal to Result*SubPointsPerLevel. Optimally this leads to gaining a number of + // levels equal to result, but this isn't always the case. + Result *= gGameExternalOptions.usExplosivesSubpointsToImprove; + StatChange( pSoldier, EXPLODEAMT, Result, TRUE ); + } + else + { + // Soldier is suffering direct stat damage. + pSoldier->stats.bExplosive = __max(1,pSoldier->stats.bExplosive + Result); + gMercProfiles[ pSoldier->ubProfile ].bExplosive = pSoldier->stats.bExplosive; + // make this stat RED for a while... + pSoldier->timeChanges.uiChangeExplosivesTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( EXP_INCREASE ); + fBadResult = TRUE; + + // Log message + BuildStatChangeString( sString, pSoldier->name, FALSE, Result, EXPLODEAMT ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[17], pSoldier->name, gzFacilityRiskResultStrings[9], szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[18], pSoldier->name, gzFacilityRiskResultStrings[9], gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + break; + case RISK_INJURY: + // This can only be a negative effect... for now. + if (Result < 0) + { + // Soldier is suffering direct unbandaged injury. + pSoldier->SoldierTakeDamage( 0, abs(Result), abs(Result), TAKE_DAMAGE_BLOODLOSS, NOBODY, NOWHERE, 0, FALSE ); + fBadResult = TRUE; + + if (pSoldier->stats.bLife > 0) + { + if (pSoldier->bBleeding > MIN_BLEEDING_THRESHOLD) + { + // Log message + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + swprintf( sString, gzFacilityErrorMessage[31], pSoldier->name, szSectorGrid ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Do Screen Message, stop time, and take character off duty immediately. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[19], pSoldier->name, szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[20], pSoldier->name, gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + AddCharacterToAnySquad( pSoldier ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + else + { + // Log message + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + swprintf( sString, gzFacilityErrorMessage[30], pSoldier->name, szSectorGrid ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + + // Soldier isn't bleeding too bad. Let the player know, but don't take any action. + // Do Screen Message and stop time. + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[21], pSoldier->name, szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[22], pSoldier->name, gFacilityTypes[ubFacilityType].szFacilityName, szSectorGrid ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + StopTimeCompression(); + } + } + } + break; + case RISK_MORALE: + pSoldier->aiData.bMorale = __min(100,__max(0, pSoldier->aiData.bMorale + Result)); + pSoldier->aiData.bStrategicMoraleMod = __min(50,__max(-50, pSoldier->aiData.bStrategicMoraleMod + Result)); + RefreshSoldierMorale( pSoldier ); + break; + case RISK_FATIGUE: + pSoldier->bBreathMax = __min(100,__max(0, pSoldier->bBreathMax + Result)); + pSoldier->bBreath = pSoldier->bBreathMax; + break; + case RISK_DRUNK: + + // Has soldier just drunk enough to make him drunk off his head? + if (GetDrunkLevel(pSoldier) == BORDERLINE) + { + // Log message + swprintf( sString, gzFacilityErrorMessage[27], pSoldier->name ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + } + + // Add effects + if ( ( pSoldier->drugs.bFutureDrugEffect[ DRUG_TYPE_ALCOHOL ] + ubDrugEffect[ DRUG_TYPE_ALCOHOL ] ) < 127 ) + { + pSoldier->drugs.bFutureDrugEffect[ DRUG_TYPE_ALCOHOL ] += ubDrugEffect[ DRUG_TYPE_ALCOHOL ]; + } + pSoldier->drugs.bDrugEffectRate[ DRUG_TYPE_ALCOHOL ] = ubDrugTravelRate[ DRUG_TYPE_ALCOHOL ]; + + // Reset once we sleep... + pSoldier->drugs.bTimesDrugUsedSinceSleep[ DRUG_TYPE_ALCOHOL ]++; + + // Increment side effects.. + if ( ( pSoldier->drugs.bDrugSideEffect[ DRUG_TYPE_ALCOHOL ] + ubDrugSideEffect[ DRUG_TYPE_ALCOHOL ] ) < 127 ) + { + pSoldier->drugs.bDrugSideEffect[ DRUG_TYPE_ALCOHOL ] += ( ubDrugSideEffect[ DRUG_TYPE_ALCOHOL ] ); + } + // Stop side effects until were done.... + pSoldier->drugs.bDrugSideEffectRate[ DRUG_TYPE_ALCOHOL ] = 0; + + // ATE: Make guy collapse from heart attack if too much stuff taken.... + if ( pSoldier->drugs.bDrugSideEffectRate[ DRUG_TYPE_ALCOHOL ] > ( ubDrugSideEffect[ DRUG_TYPE_ALCOHOL ] * 3 ) ) + { + if ( pSoldier->ubProfile == LARRY_NORMAL ) + { + pSoldier = SwapLarrysProfiles( pSoldier ); + } + else if ( pSoldier->ubProfile == LARRY_DRUNK ) + { + gMercProfiles[ LARRY_DRUNK ].bNPCData = 0; + } + + // Keel over... + DeductPoints( pSoldier, 0, 10000 ); + + // Permanently lower certain stats... + pSoldier->stats.bWisdom = __max(1, pSoldier->stats.bWisdom-5); + pSoldier->stats.bDexterity = __max(1, pSoldier->stats.bDexterity-5); + pSoldier->stats.bStrength = __max(1, pSoldier->stats.bStrength-5); + + // export stat changes to profile + gMercProfiles[ pSoldier->ubProfile ].bWisdom = pSoldier->stats.bWisdom; + gMercProfiles[ pSoldier->ubProfile ].bDexterity = pSoldier->stats.bDexterity; + gMercProfiles[ pSoldier->ubProfile ].bStrength = pSoldier->stats.bStrength; + + // make those stats RED for a while... + pSoldier->timeChanges.uiChangeWisdomTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( WIS_INCREASE ); + pSoldier->timeChanges.uiChangeDexterityTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( DEX_INCREASE ); + pSoldier->timeChanges.uiChangeStrengthTime = GetJA2Clock(); + pSoldier->usValueGoneUp &= ~( STRENGTH_INCREASE ); + } + break; + case RISK_LOYALTY_LOCAL: + Result *= GAIN_PTS_PER_LOYALTY_PT; // Transform points into actual loyalty. + if (Result > 0) + { + IncrementTownLoyalty( ubTownID, Result ); + } + else + { + DecrementTownLoyalty( ubTownID, abs(Result) ); + // Log message + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[23], pTownNames[ubTownID], pSoldier->name ); + else + swprintf( sString, gzFacilityErrorMessage[24], pTownNames[ubTownID], pSoldier->name, gFacilityTypes[ubFacilityType].szFacilityName ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + } + break; + case RISK_LOYALTY_GLOBAL: + Result *= GAIN_PTS_PER_LOYALTY_PT; // Transform points into actual loyalty. + if (Result > 0) + { + IncrementTownLoyaltyEverywhere( Result ); + } + else + { + DecrementTownLoyaltyEverywhere( abs(Result) ); + // Log message + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, szSectorGrid ); + if (!fOperatingFacility) + swprintf( sString, gzFacilityErrorMessage[25], pSoldier->name, szSectorGrid ); + else + swprintf( sString, gzFacilityErrorMessage[26], pSoldier->name, szSectorGrid, gFacilityTypes[ubFacilityType].szFacilityName ); + ScreenMsg( usColor, MSG_INTERFACE, sString ); + } + break; + default: + break; + } + if (fBadResult) + { + StopTimeCompression(); + } + } + } + } +} + +INT32 GetTotalFacilityHourlyCosts( BOOLEAN fPositive ) +{ + SOLDIERTYPE *pSoldier; + INT32 iTotal = 0; + + UINT8 ubCounter = 0; + + // Run through list of player-characters + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + + // Is character truly valid? + if( !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && + pSoldier->bSectorZ == 0 && + pSoldier->stats.bLife >= OKLIFE && + !(pSoldier->flags.fMercAsleep) ) + { + UINT8 ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + if (ubAssignmentType == -1) + { + // Skip this character. + ubCounter++; + continue; + } + + UINT8 ubSector = SECTOR(pSoldier->sSectorX, pSoldier->sSectorY); + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + + if (!fPositive && // We want facilities that cost money to operate + gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour > 0) // This facility costs money + { + iTotal += gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour; + } + else if (fPositive && // We want facilities that GENERATE money + gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour < 0) + { + // Flip the value to positive + iTotal += (-1 * gFacilityTypes[ubFacilityType].AssignmentData[ubAssignmentType].sCostPerHour); + } + } + ubCounter++; + } + + return (iTotal); +} + +void InitFacilities() +{ + // Initialize facility debt. + giTotalOwedForFacilityOperationsToday = 0; + gfOutstandingFacilityDebt = FALSE; +} diff --git a/Strategic/Facilities.h b/Strategic/Facilities.h new file mode 100644 index 00000000..520c501f --- /dev/null +++ b/Strategic/Facilities.h @@ -0,0 +1,78 @@ +#ifndef _FACILITIES_H +#define _FACILITIES_H + +#include "campaign types.h" +#include "Soldier Control.h" + +// Get a specific modifier for a specific assignment at a specific facility. Includes Ambient (non-assignment based) +// effects, if any. +INT16 GetFacilityModifier( UINT8 ubModifierType, UINT8 ubFacilityType, UINT8 ubAssignmentType ); +// Get a specific modifier from all facilities in the soldier's sector. This utilizes the above function. +INT16 GetSectorModifier( SOLDIERTYPE *pSoldier, UINT8 ubModifierType ); + +// Level of awareness to enemy movements, bestowed by facilities +extern void UpdateStrategicDetectionLevel( ); + +// Adjustment to Skyrider's cost-per-tile, bestowed by facilities +extern void UpdateSkyriderCostModifier( ); +extern INT16 gsSkyriderCostModifier; // This variable is SAVED/LOADED + +// HEADROCK HAM 3.6: Strategic info variable, total of costs accumulated for the use of facilities today. Deducted +// from account and reset at midnight, unless player can't pay (in which case, carries over to the next day). +extern INT32 giTotalOwedForFacilityOperationsToday; // This variable is SAVED and LOADED. +// Do we have outstanding debts for facility operation? +extern BOOLEAN gfOutstandingFacilityDebt; // This variable is SAVED and LOADED. + +// Place this here for facility staffing checks. +extern UINT32 GetWorldTotalMin( ); + +// These are all the different modifiers we could be checking for. +enum +{ + FACILITY_PERFORMANCE_MOD = 0, + FACILITY_SLEEP_MOD, + FACILITY_FATIGUE_MOD, + FACILITY_KIT_DEGRADE_MOD, + + FACILITY_MAX_MORALE, + FACILITY_MAX_BREATH, + + FACILITY_DETECT_IMMEDIATE, + FACILITY_DETECT_DYNAMIC, + FACILITY_DETECT_LONGRANGE, + FACILITY_DETECT_ANYWHERE, + FACILITY_COUNT_INWILD, + FACILITY_COUNT_INCITIES, + + FACILITY_MINE_INCOME_MOD, + FACILITY_SKYRIDER_COST_MOD, +}; + +// HEADROCK HAM 3.6: Different enemy detection and counting levels +enum +{ + DETECT_ENEMIES_DYNAMIC = 0, + DETECT_ENEMIES_LONGRANGE, + DETECT_ENEMIES_ANYWHERE, + + COUNT_ENEMIES_IN_WILD, + COUNT_ENEMIES_IN_CITIES, +}; + +void UpdateGlobalVariablesFromFacilities( void ); +void UpdateFacilityUsageCosts( void ); + +void HandleDailyPaymentFacilityDebt( void ); +void HandleManualPaymentFacilityDebt( void ); +void HandleHourlyRisks( void ); + +INT32 MineIncomeModifierFromFacility( UINT8 ubMine ); +INT8 GetSoldierFacilityAssignmentIndex( SOLDIERTYPE *pSoldier ); +void HandleRisksForSoldier( SOLDIERTYPE *pSoldier ); +void HandleRisksForSoldierFacilityAssignment( SOLDIERTYPE *pSoldier, UINT8 ubFacilityType, UINT8 ubAssignmentType ); +INT16 FacilityRiskResult( SOLDIERTYPE *pSoldier, UINT8 ubRiskType, UINT8 ubFacilityType, UINT8 ubAssignmentType ); +INT32 GetTotalFacilityHourlyCosts( BOOLEAN fPositive ); + +void InitFacilities(); + +#endif \ No newline at end of file diff --git a/Strategic/Game Init.cpp b/Strategic/Game Init.cpp index ba511d0c..2a928edc 100644 --- a/Strategic/Game Init.cpp +++ b/Strategic/Game Init.cpp @@ -61,6 +61,8 @@ #include "Interface.h" #include "cheats.h" #include "Interface Panels.h" + // HEADROCK HAM 3.6: Include for adding facility debt reset + #include "Facilities.h" #endif #include "Vehicles.h" @@ -233,6 +235,64 @@ void InitNPCs( void ) void InitBloodCatSectors() { + /////////////////////////////////////////////////// + // HEADROCK HAM 3.6: Bloodcat Placement Externalization + // + // It is now possible for the user/modder to set the maximum number of bloodcats that may appear in the sector. + // We can now place bloodcats anywhere we'd like (aboveground), stretching the limits of maps without disturbing + // the game. The placements defined here are still the absolute MAXIMUM, so if the map has more placements available + // they will be ignored. + // Please note that if the mapfile has LESS placements than indicated here, then the game will reduce the number of + // placements automatically to fit the MAP, not the XML. + + UINT16 x = 0; + UINT8 PlacementType = 0; + UINT8 ubMaxBloodcats = 0; + UINT8 ubDifficulty = gGameOptions.ubDifficultyLevel-1; // This way, novice=0. + INT16 ubLairSectorId = -1; // Location of ONE lair sector (the first one found) + + // Clean all bloodcat data. + for( x = 0; x < 256; x++ ) + { + SectorInfo[ x ].bBloodCats = 0; + SectorInfo[ x ].bBloodCatPlacements = 0; + } + + // Begin loop for every sector. + for (x = 0; x < 256; x++) + { + // Set "maximum placements" value to whatever is defined in the XML file for this difficulty level. + SectorInfo[ x ].bBloodCatPlacements = gBloodcatPlacements[x][ubDifficulty].ubMaxBloodcats; + + PlacementType = gBloodcatPlacements[x][0].PlacementType; + + // Record the first sector you can find which is defined as a LAIR sector. Only one sector can serve + // this purpose, because the quests rely on it. + if (PlacementType == BLOODCAT_PLACEMENT_LAIR && ubLairSectorId == -1) + { + ubLairSectorId = x; + } + + if (PlacementType != 0) + { + // Sector is a lair or static bloodcat sector. Fill it up with actual bloodcats please. + SectorInfo[ x ].bBloodCats = gBloodcatPlacements[x][ubDifficulty].ubMaxBloodcats; + // "Ambush" sectors (PlacementType 0) do not have bloodcats in them until triggered. + } + } + + if (ubLairSectorId >= 0) + { + gubBloodcatLairSectorId = (UINT8)ubLairSectorId; + } + else + { + THROWEXCEPTION(L"At least one Bloodcat Lair must be defined in BloodcatPlacements.XML!"); + } + + /* + + INT32 i; //Hard coded table of bloodcat populations. We don't have //access to the real population (if different) until we physically @@ -296,7 +356,7 @@ void InitBloodCatSectors() SectorInfo[ SEC_N5 ].bBloodCatPlacements = 18; SectorInfo[ SEC_N5 ].bBloodCats = 18; break; - } + }*/ } @@ -334,6 +394,8 @@ void InitStrategicLayer( void ) // make Orta, Tixa, SAM sites not found InitMapSecrets(); + // HEADROCK HAM 3.6: Initialize facilities. + InitFacilities(); // free up any leave list arrays that were left allocated ShutDownLeaveList( ); @@ -344,7 +406,8 @@ void InitStrategicLayer( void ) SetGameTimeCompressionLevel( TIME_COMPRESS_X0 ); // select A9 Omerta as the initial selected sector - ChangeSelectedMapSector( startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Actually, this is where we set the starting sector based on external variables. + ChangeSelectedMapSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); // Reset these flags or mapscreen could be disabled and cause major headache. fDisableDueToBattleRoster = FALSE; @@ -498,6 +561,10 @@ BOOLEAN InitNewGame( BOOLEAN fReset ) AddFutureDayStrategicEvent( EVENT_DAY3_ADD_EMAIL_FROM_SPECK, 60*7, 0, uiDaysTimeMercSiteAvailable ); } + // HEADROCK HAM 3.5: Reset LZ to default arrival + gsMercArriveSectorX = gGameExternalOptions.ubDefaultArrivalSectorX; + gsMercArriveSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; + #ifdef CRIPPLED_VERSION { UINT32 cnt; @@ -864,6 +931,15 @@ void ReStartingGame() //Reload the Merc profiles LoadMercProfiles( ); + if (gGameExternalOptions.fWriteProfileDataToXML) + { + // HEADROCK PROFEX: Create mercprofiles xml output + WriteMercProfiles(); + + // HEADROCK PROFEX: Create mercopinions xml output + WriteMercOpinions(); + } + // Reload quote files ReloadAllQuoteFiles(); diff --git a/Strategic/Hourly Update.cpp b/Strategic/Hourly Update.cpp index 198a14be..b6304498 100644 --- a/Strategic/Hourly Update.cpp +++ b/Strategic/Hourly Update.cpp @@ -24,6 +24,8 @@ #include "SaveLoadGame.h" #include "GameSettings.h" #include "connect.h" +// HEADROCK HAM 3.5: Add facility code for hourly update of detection levels +#include "Facilities.h" void HourlyQuestUpdate( void ); void HourlyLarryUpdate( void ); @@ -56,9 +58,17 @@ void HandleHourlyUpdate() // hourly update of team assignments UpdateAssignments(); + // HEADROCK HAM 3.5: Tells the rest of the program whether we've got mercs working on detecting enemy units. + UpdateStrategicDetectionLevel(); + + // HEADROCK HAM 3.5: Re-calculates a modifier to Skyrider's flight costs based on mercs working in specific facilities. + UpdateSkyriderCostModifier(); + // hourly update of hated/liked mercs UpdateBuddyAndHatedCounters(); + HandleHourlyRisks(); + // update morale! HourlyMoraleUpdate(); diff --git a/Strategic/Map Screen Helicopter.cpp b/Strategic/Map Screen Helicopter.cpp index 91a1cf02..36027d92 100644 --- a/Strategic/Map Screen Helicopter.cpp +++ b/Strategic/Map Screen Helicopter.cpp @@ -38,6 +38,8 @@ #include "soundman.h" #include "Isometric Utils.h" #include "Scheduling.h" + // HEADROCK HAM 3.5: Added facility-based skyrider costs modifier + #include "Facilities.h" #endif @@ -527,16 +529,23 @@ INT32 GetCostOfPassageForHelicopter( INT16 sX, INT16 sY ) // check if sector is air controlled or not, if so, then normal cost, otherwise increase the cost INT32 iCost = 0; + // HEADROCK HAM 3.5: Costs externalized + INT32 iBaseCostPerGreenTile = gGameExternalOptions.usHelicopterBaseCostPerGreenTile; + INT32 iBaseCostPerRedTile = gGameExternalOptions.usHelicopterBaseCostPerRedTile; + // if they don't control it if( StrategicMap[ CALCULATE_STRATEGIC_INDEX( sX, sY ) ].fEnemyAirControlled == FALSE ) { - iCost = COST_AIRSPACE_SAFE; + iCost = iBaseCostPerGreenTile; } else { - iCost = COST_AIRSPACE_UNSAFE; + iCost = iBaseCostPerRedTile; } + // HEADROCK HAM 3.5: Apply facility-based modifiers from global integer recalculated hourly + iCost = __max(0, iCost + gsSkyriderCostModifier); + return( iCost ); } diff --git a/Strategic/Map Screen Interface Border.cpp b/Strategic/Map Screen Interface Border.cpp index c9ccd8f4..ef13809f 100644 --- a/Strategic/Map Screen Interface Border.cpp +++ b/Strategic/Map Screen Interface Border.cpp @@ -773,7 +773,9 @@ void ToggleShowMilitiaMode( void ) // no - so put up a message explaining how it works // if he's already training some - if( IsAnyOneOnPlayersTeamOnThisAssignment( TRAIN_TOWN ) ) + // HEADROCK HAM 3.6: Also for Mobile trainers. + if( IsAnyOneOnPlayersTeamOnThisAssignment( TRAIN_TOWN ) || + IsAnyOneOnPlayersTeamOnThisAssignment( TRAIN_MOBILE ) ) { // say they'll show up when training is completed pwString = pMapErrorString[ 28 ]; diff --git a/Strategic/Map Screen Interface Bottom.cpp b/Strategic/Map Screen Interface Bottom.cpp index 7811a474..40141b99 100644 --- a/Strategic/Map Screen Interface Bottom.cpp +++ b/Strategic/Map Screen Interface Bottom.cpp @@ -68,8 +68,12 @@ want to have the new inventory panel not overlap the message text area. */ #define MESSAGE_SCROLL_AREA_HEIGHT ( MESSAGE_SCROLL_AREA_END_Y - MESSAGE_SCROLL_AREA_START_Y + 1 ) // CHRISL: Use these if we want scroll bar based on left edge of screen -#define MESSAGE_SCROLL_AREA_START_X 330 -#define MESSAGE_SCROLL_AREA_END_X 344 +// HEADROCK HAM 3.6: Now global variables to be changed when initializing map mode. +// See InitMapScreenInterfaceBottomCoords() +//#define MESSAGE_SCROLL_AREA_START_X 330 +//#define MESSAGE_SCROLL_AREA_END_X 344 +UINT16 MESSAGE_SCROLL_AREA_START_X; +UINT16 MESSAGE_SCROLL_AREA_END_X; #define SLIDER_HEIGHT 11 #define SLIDER_WIDTH 11 @@ -184,6 +188,7 @@ void DeleteMapScreenBottomMessageScrollRegion( void ); void DisplayCurrentBalanceForMapBottom( void ); void DisplayCurrentBalanceTitleForMapBottom( void ); void DisplayProjectedDailyMineIncome( void ); +void DisplayProjectedDailyExpenses( void ); // HEADROCK HAM 3.6: New expenses window shows running costs today. void DrawNameOfLoadedSector( void ); void EnableDisableBottomButtonsAndRegions( void ); @@ -296,7 +301,8 @@ void RenderMapScreenInterfaceBottom( BOOLEAN fForceMapscreenFullRender ) // render whole panel // HEADROCK Changed this line to accept outside influence through the new boolean: //if ( fForceMapscreenFullRender == TRUE || fMapScreenBottomDirty == TRUE) - if ( fMapScreenBottomDirty ) + // HEADROCK HAM 3.6: OK, let's always render this panel, as long as the team inventory screen isn't open. + if ( fMapScreenBottomDirty || !fShowInventoryFlag ) { // get and blt panel GetVideoObject(&hHandle, guiMAPBOTTOMPANEL ); @@ -334,12 +340,20 @@ void RenderMapScreenInterfaceBottom( BOOLEAN fForceMapscreenFullRender ) // Headrock: Moved this next line from the above marker ^ fMapScreenBottomDirty = FALSE; + + // CHRISL: Don't display messagelist if inventory panel is open + // CHRISL: Only run this condition if we're drawing the message list on the left + // display messages that can be scrolled through + // HEADROCK HAM 3.6: Hmmmm, why not draw them? LEt's just move this whole segment here, shall we? + //if(!fShowInventoryFlag) + DisplayStringsInMapScreenMessageList( ); } DisplayCompressMode( ); DisplayCurrentBalanceForMapBottom( ); DisplayProjectedDailyMineIncome( ); + DisplayProjectedDailyExpenses( ); // draw the name of the loaded sector DrawNameOfLoadedSector( ); @@ -347,12 +361,7 @@ void RenderMapScreenInterfaceBottom( BOOLEAN fForceMapscreenFullRender ) // display slider on the scroll bar DisplayScrollBarSlider( ); - // CHRISL: Don't display messagelist if inventory panel is open - // CHRISL: Only run this condition if we're drawing the message list on the left - // display messages that can be scrolled through - if(!fShowInventoryFlag) - DisplayStringsInMapScreenMessageList( ); - + // handle auto scroll //CheckForAndHandleAutoMessageScroll( ); @@ -427,10 +436,12 @@ BOOLEAN CreateButtonsForMapScreenInterfaceBottom( void ) // BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST - 1, // (GUI_CALLBACK)BtnGenericMouseMoveButtonCallback, (GUI_CALLBACK)BtnMessageDownMapScreenCallback); // CHRISL: Use these if we want buttons based on left edge of screen - guiMapMessageScrollButtons[ MAP_SCROLL_MESSAGE_UP ] = QuickCreateButton( guiMapMessageScrollButtonsImage[ MAP_SCROLL_MESSAGE_UP ], 331, (SCREEN_HEIGHT - 109), +// HEADROCK HAM 3.6: Message window is now as wide as possible. + + guiMapMessageScrollButtons[ MAP_SCROLL_MESSAGE_UP ] = QuickCreateButton( guiMapMessageScrollButtonsImage[ MAP_SCROLL_MESSAGE_UP ], (SCREEN_WIDTH - 306), (SCREEN_HEIGHT - 109), BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST - 1, (GUI_CALLBACK)BtnGenericMouseMoveButtonCallback, (GUI_CALLBACK)BtnMessageUpMapScreenCallback); - guiMapMessageScrollButtons[ MAP_SCROLL_MESSAGE_DOWN ] = QuickCreateButton( guiMapMessageScrollButtonsImage[ MAP_SCROLL_MESSAGE_DOWN ], 331, (SCREEN_HEIGHT - 28), + guiMapMessageScrollButtons[ MAP_SCROLL_MESSAGE_DOWN ] = QuickCreateButton( guiMapMessageScrollButtonsImage[ MAP_SCROLL_MESSAGE_DOWN ], (SCREEN_WIDTH - 306), (SCREEN_HEIGHT - 28), BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST - 1, (GUI_CALLBACK)BtnGenericMouseMoveButtonCallback, (GUI_CALLBACK)BtnMessageDownMapScreenCallback); @@ -1334,7 +1345,8 @@ BOOLEAN AllowedToTimeCompress( void ) return( FALSE ); } - if( fShowAssignmentMenu || fShowTrainingMenu || fShowAttributeMenu || fShowSquadMenu || fShowContractMenu || fShowRemoveMenu ) + // HEADROCK HAM 3.6: Added Facility Menus + if( fShowAssignmentMenu || fShowTrainingMenu || fShowAttributeMenu || fShowSquadMenu || fShowContractMenu || fShowRemoveMenu || fShowFacilityAssignmentMenu || fShowFacilityMenu ) { return( FALSE ); } @@ -1428,7 +1440,9 @@ void DisplayCurrentBalanceTitleForMapBottom( void ) // CHRISL: Replaced X coordinate with dynamic coordinate set from right edge of screen //VarFindFontCenterCoordinates( (SCREEN_WIDTH - 637), (SCREEN_HEIGHT - 107), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // CHRISL: Use this if we want to display from the left edge - VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 107), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + //VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 107), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + // HEADROCK HAM 3.6: The balance/income box has been moved to the right side, near the laptop button. + VarFindFontCenterCoordinates( (SCREEN_WIDTH - 278), (SCREEN_HEIGHT - 111), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // print it mprintf( sFontX, sFontY, L"%s", sString ); @@ -1439,7 +1453,18 @@ void DisplayCurrentBalanceTitleForMapBottom( void ) // CHRISL: Replaced X coordinate with dynamic coordinate set from right edge of screen //VarFindFontCenterCoordinates( (SCREEN_WIDTH - 637), (SCREEN_HEIGHT - 61), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // CHRISL: Use this if we want to display from the left edge - VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 61), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + //VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 61), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + // HEADROCK HAM 3.6: The balance/income box has been moved to the right side, near the laptop button. + VarFindFontCenterCoordinates( (SCREEN_WIDTH - 278), (SCREEN_HEIGHT - 74), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + + // print it + mprintf( sFontX, sFontY, L"%s", sString ); + + swprintf( sString, L"%s", New113HAMMessage[ 20 ] ); + + // HEADROCK HAM 3.6: Projected expenses for today, with facilities (and in the future, merc contracts) taken into + // account. + VarFindFontCenterCoordinates( (SCREEN_WIDTH - 278), (SCREEN_HEIGHT - 37), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // print it mprintf( sFontX, sFontY, L"%s", sString ); @@ -1474,7 +1499,9 @@ void DisplayCurrentBalanceForMapBottom( void ) // CHRISL: Replaced X coordinate with dynamic coordinate set from right edge of screen //VarFindFontCenterCoordinates( (SCREEN_WIDTH - 637), (SCREEN_HEIGHT - 91), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // CHRISL: Use this if we want to display from the left edge - VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 91), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + //VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 91), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + // HEADROCK HAM 3.6: The balance/income box has been moved to the right side, near the laptop button. + VarFindFontCenterCoordinates( (SCREEN_WIDTH - 278), (SCREEN_HEIGHT - 95), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // print it mprintf( sFontX, sFontY, L"%s", sString ); @@ -1588,7 +1615,9 @@ void DisplayProjectedDailyMineIncome( void ) // CHRISL: Replaced X coordinate with dynamic coordinate set from right edge of screen //VarFindFontCenterCoordinates( (SCREEN_WIDTH - 637), (SCREEN_HEIGHT - 45), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // CHRISL: Use this if we want to display from the left edge - VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 45), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + //VarFindFontCenterCoordinates( 359, (SCREEN_HEIGHT - 45), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + // HEADROCK HAM 3.6: The balance/income box has been moved to the right side, near the laptop button. + VarFindFontCenterCoordinates( (SCREEN_WIDTH - 278), (SCREEN_HEIGHT - 58), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); // print it mprintf( sFontX, sFontY, L"%s", sString ); @@ -1596,6 +1625,58 @@ void DisplayProjectedDailyMineIncome( void ) return; } +void DisplayProjectedDailyExpenses( void ) +{ + INT32 iRate = 0; + static INT32 iOldExpensesRate = -1; + CHAR16 sString[ 128 ]; + INT16 sFontX, sFontY; + + // grab the rate from the financial system + iRate = GetProjectedExpenses( ); + + if( iRate != iOldExpensesRate ) + { + iOldExpensesRate = iRate; + fMapScreenBottomDirty = TRUE; + + // if screen was not dirtied, leave + if( fMapBottomDirtied == FALSE ) + { + return; + } + } + + // set the font buffer + SetFontDestBuffer( FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); + + // set up the font + SetFont( COMPFONT ); + if (iRate == 0) + { + SetFontForeground( 183 ); + } + else + { + SetFontForeground( FONT_MCOLOR_LTRED ); + } + SetFontBackground( FONT_BLACK ); + + swprintf( sString, L"%d", iRate ); + + // insert extra characters + InsertCommasForDollarFigure( sString ); + InsertDollarSignInToString( sString ); + + // center it + VarFindFontCenterCoordinates( (SCREEN_WIDTH - 278), (SCREEN_HEIGHT - 21), 78, 10, COMPFONT, &sFontX, &sFontY, sString ); + + // print it + mprintf( sFontX, sFontY, L"%s", sString ); + + return; +} + BOOLEAN CommonTimeCompressionChecks( void ) { @@ -1950,3 +2031,11 @@ void ChangeCurrentMapscreenMessageIndex( UINT8 ubNewMessageIndex ) // refresh screen fMapScreenBottomDirty = TRUE; } + +void InitMapScreenInterfaceBottomCoords( void ) +{ + // HEADROCK HAM 3.6: Message window is now as wide as possible. + MESSAGE_SCROLL_AREA_START_X = SCREEN_WIDTH - 307; + MESSAGE_SCROLL_AREA_END_X = SCREEN_WIDTH - 293; +} + diff --git a/Strategic/Map Screen Interface Bottom.h b/Strategic/Map Screen Interface Bottom.h index 54950ea6..b72d34f9 100644 --- a/Strategic/Map Screen Interface Bottom.h +++ b/Strategic/Map Screen Interface Bottom.h @@ -65,4 +65,7 @@ void MapScreenMsgScrollUp( UINT8 ubLinesUp ); void ChangeCurrentMapscreenMessageIndex( UINT8 ubNewMessageIndex ); void MoveToEndOfMapScreenMessageList( void ); +// HEADROCK HAM 3.6: Reset coordinates for slider bar and message window +void InitMapScreenInterfaceBottomCoords( void ); + #endif \ No newline at end of file diff --git a/Strategic/Map Screen Interface Map Inventory.cpp b/Strategic/Map Screen Interface Map Inventory.cpp index 409d5b6f..f7c07439 100644 --- a/Strategic/Map Screen Interface Map Inventory.cpp +++ b/Strategic/Map Screen Interface Map Inventory.cpp @@ -582,6 +582,8 @@ void CreateDestroyMapInventoryPoolButtons( BOOLEAN fExitFromMapScreen ) fMapPanelDirty = TRUE; fTeamPanelDirty = TRUE; fCharacterInfoPanelDirty = TRUE; + // HEADROCK HAM 3.6: Yet again, make sure to refresh the bottom panel on closing. + fMapScreenBottomDirty = TRUE; //CHRISL: close stack popup and item description windows DeleteItemStackPopup(); @@ -1471,8 +1473,6 @@ void BeginInventoryPoolPtr( OBJECTTYPE *pInventorySlot ) { for( UINT32 iNumber = 0 ; iNumber < pInventoryPoolList.size() ; ++iNumber) { - // WANNE: Fix by Headrock - //if ( pInventoryPoolList[ iNumber ].object.usItem == gItemPointer.usItem ) if ( pInventoryPoolList[ iNumber ].object.usItem == gItemPointer.usItem && pInventoryPoolList[ iNumber ].usFlags & WORLD_ITEM_REACHABLE) { iPrice += SellItem( pInventoryPoolList[ iNumber ].object ); diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index 8b5702d2..4c3a8dd9 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -48,6 +48,9 @@ #include "Quests.h" #include "connect.h" +// HEADROCK HAM 3.5: included for detectionlevels +#include "Facilities.h" +#include "MilitiaSquads.h" // zoom x and y coords for map scrolling INT32 iZoomX = 0; @@ -355,8 +358,9 @@ extern BOOLEAN fMapScreenBottomDirty; BOOLEAN fFoundTixa = FALSE; // selected sector -UINT16 sSelMapX = 9; -UINT16 sSelMapY = 1; +// HEADROCK HAM 3.5: Default selected sector doesn't have to be Omerta does it? :) +UINT16 sSelMapX = gGameExternalOptions.ubDefaultArrivalSectorX; +UINT16 sSelMapY = gGameExternalOptions.ubDefaultArrivalSectorY; // highlighted sector INT16 gsHighlightSectorX=-1; @@ -522,6 +526,7 @@ void CreateMilitiaPanelBottomButton( void ); void DeleteMilitiaPanelBottomButton( void ); void MilitiaDoneButtonCallback(GUI_BUTTON *btn,INT32 reason); void MilitiaAutoButtonCallback(GUI_BUTTON *btn,INT32 reason); +void MilitiaDisbandYesNoBoxCallback( UINT8 bExitValue ); // HEADROCK HAM 3.6: Disband militia callback void RenderShadingForUnControlledSectors( void ); void DrawTownMilitiaForcesOnMap( void ); void CheckAndUpdateStatesOfSelectedMilitiaSectorButtons( void ); @@ -4381,7 +4386,10 @@ void DisplayDistancesForHelicopter( void ) // calculate the cost of the trip based on the number of safe and unsafe sectors it will pass through - uiTripCost = ( sNumSafeSectors * COST_AIRSPACE_SAFE ) + ( sNumUnSafeSectors * COST_AIRSPACE_UNSAFE ); + // HEADROCK HAM 3.5: Externalized Base Cost. Also includes hourly-calculated facility modifier. + UINT32 uiCostGreen = __max(0,gGameExternalOptions.usHelicopterBaseCostPerGreenTile + gsSkyriderCostModifier); + UINT32 uiCostRed = __max(0,gGameExternalOptions.usHelicopterBaseCostPerRedTile + gsSkyriderCostModifier); + uiTripCost = ( sNumSafeSectors * uiCostGreen ) + ( sNumUnSafeSectors * uiCostRed ); swprintf( sString, L"%d", uiTripCost ); InsertCommasForDollarFigure( sString ); @@ -6092,13 +6100,22 @@ void MilitiaDoneButtonCallback(GUI_BUTTON *btn,INT32 reason) { btn->uiFlags &= ~( BUTTON_CLICKED_ON ); - // reset fact we are in the box - sSelectedMilitiaTown = 0; - fMapPanelDirty = TRUE; - // Go ahead and reset the militia in the selected sector (even if we didn't change it here) - if (gfStrategicMilitiaChangesMade) + // HEADROCK HAM 3.6: Clicking the done button while militia are on the cursor causes a pop-up + // for authorizing DISBANDING those militia. + if (sGreensOnCursor || sRegularsOnCursor || sElitesOnCursor) { - ResetMilitia(); + DoMapMessageBox( MSG_BOX_BASIC_STYLE, pMilitiaString[ 3 ], MAP_SCREEN, MSG_BOX_FLAG_YESNO, MilitiaDisbandYesNoBoxCallback ); + } + else + { + // reset fact we are in the box + sSelectedMilitiaTown = 0; + fMapPanelDirty = TRUE; + // Go ahead and reset the militia in the selected sector (even if we didn't change it here) + if (gfStrategicMilitiaChangesMade) + { + ResetMilitia(); + } } } } @@ -6279,13 +6296,17 @@ void CheckAndUpdateStatesOfSelectedMilitiaSectorButtons( void ) iNumberOfRegulars = SectorInfo[ sGlobalMapSector ].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] + sRegularsOnCursor; iNumberOfElites = SectorInfo[ sGlobalMapSector ].ubNumberOfCivsAtLevel[ ELITE_MILITIA ] + sElitesOnCursor; + // HEADROCK HAM 3.6: This button is no longer disabled when there are unassigned militia. In fact, + // clicking the button when militia are unassigned will prompt the player to authorize disbanding them. if( ( sGreensOnCursor > 0 ) || ( sRegularsOnCursor > 0 ) || ( sElitesOnCursor > 0 ) ) { - DisableButton( giMapMilitiaButton[ 4 ] ); // DONE + // DisableButton( giMapMilitiaButton[ 4 ] ); // DONE + SpecifyButtonText( giMapMilitiaButton[ 4 ], pMilitiaButtonString[ 2 ] ); } else { - EnableButton( giMapMilitiaButton[ 4 ] ); // DONE + // EnableButton( giMapMilitiaButton[ 4 ] ); // DONE + SpecifyButtonText( giMapMilitiaButton[ 4 ], pMilitiaButtonString[ 1 ] ); } // greens button @@ -6445,34 +6466,59 @@ void ClearAnySectorsFlashingNumberOfEnemies() UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) { UINT32 uiSectorFlags = SectorInfo[ SECTOR( sSectorX, sSectorY ) ].uiFlags; + BOOLEAN fDetection = FALSE; + BOOLEAN fCount = FALSE; + Assert(sSectorX > 0 && sSectorY > 0 && sSectorX < 17 && sSectorY < 17); - // if player has militia close enough to scout this sector out, if there are mercs who can scout here, OR - //Special case flag used when players encounter enemies in a sector, then retreat. The number of enemies - //will display on mapscreen until time is compressed. When time is compressed, the flag is cleared, and - //a question mark is displayed to reflect that the player no longer knows. + // HEADROCK HAM 3.5: There are now several ways to control the detection of enemy units. This function has been revamped + // to consider all options. Depending on externalized settings, different conditions yield different results. + // + // Option #1: Vanilla Default. + // It is possible to detect all enemies in explored territory, including cities. + // Militia in an adjacent sector can count how many enemies are there, except standing garrisons. + // Option #2: HAM 3.2. No Detection without Recon. + // Mobile enemy groups are only detected if there are militia in any adjacent sector. Otherwise, they can + // travel through explored sectors without being seen. + // + // Option #3: This is a new feature for HAM 3.5, which ties together with Facility Externalization. + // In simple words, facilities can enhance detection abilities. Facilities that do so may be placed around the + // map in any sector. When a merc with sufficient wisdom is resting here, he increases the detection "level", + // making it easier to detect or even count enemies in distant sectors. + // This option can be combined with either of the two above. + + + // Detection through active recon. + // Mercs provide recon in the same sector they're in. + // Militia provide recon in any adjacent sector (diagonals included) + // There's also a special case flag used when players encounter enemies in a sector, then retreat. You can only + // see the size of their force while the clock is paused. When unpaused, the flag is reset. if ( CanMercsScoutThisSector( sSectorX, sSectorY, 0 ) || CanNearbyMilitiaScoutThisSector( sSectorX, sSectorY ) || ( uiSectorFlags & SF_PLAYER_KNOWS_ENEMIES_ARE_HERE ) ) { - // if the enemies are stationary (i.e. mercs attacking a garrison) - if ( NumStationaryEnemiesInSector( sSectorX, sSectorY ) > 0 ) + fDetection = TRUE; + + // if all enemies are mobile (I.E. not garrison troops) + if ( NumStationaryEnemiesInSector( sSectorX, sSectorY ) == 0 ) { - // inside a garrison - hide their # (show question mark) to match what the PBI is showing - return KNOWS_THEYRE_THERE; - } - else - { - // other situations - show exactly how many there are - return KNOWS_HOW_MANY; + // They're also counted by default. + fCount = TRUE; } } - // if the player has visited the sector during this game + // Explored Sector Detection + // Enemy can be detected in any previously-visited sector. + // This is also enabled by some facilities, provided a merc is present and available to do it. if( GetSectorFlagStatus( sSectorX, sSectorY, 0, SF_ALREADY_VISITED ) == TRUE ) { - // then he always knows about any enemy presence for the remainder of the game, but not exact numbers - return KNOWS_THEYRE_THERE; + // HEADROCK HAM 3.2: When enabled, this INI setting disallows detection of enemy roamers beyond merc/militia + // recon range. + if (!gGameExternalOptions.fNoEnemyDetectionWithoutRecon) + { + // then he always knows about any enemy presence for the remainder of the game, but not exact numbers + fDetection = TRUE; + } } // if Skyrider noticed the enemis in the sector recently @@ -6482,7 +6528,7 @@ UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) if( IsSkyriderIsFlyingInSector( sSectorX, sSectorY ) ) { // player remains aware of them as long as Skyrider remains in the sector - return KNOWS_THEYRE_THERE; + fDetection = TRUE; } else { @@ -6491,9 +6537,32 @@ UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) } } + // Facilities can set a flag that allows detection in some sectors. We can read flags directly from the sector + // data to know whether we should show the enemies there. This overrides ANYTHING else. + if (SectorInfo[ SECTOR( sSectorX, sSectorY ) ].ubDetectionLevel & 1) + { + fDetection = TRUE; + } + if (SectorInfo[ SECTOR( sSectorX, sSectorY ) ].ubDetectionLevel & (1<<2) ) + { + fCount = TRUE; + } - // no information available - return KNOWS_NOTHING; + if (!fDetection) + { + // no information available + return KNOWS_NOTHING; + } + else if (!fCount) + { + // No accurate information + return KNOWS_THEYRE_THERE; + } + else + { + // Accurate information + return KNOWS_HOW_MANY; + } } @@ -7126,3 +7195,38 @@ BOOLEAN CanRedistributeMilitiaInSector( INT16 sClickedSectorX, INT16 sClickedSec // the fight is elsewhere - ok to redistribute return( TRUE ); } + +// Callback for disbanding militia in the Militia Box +void MilitiaDisbandYesNoBoxCallback( UINT8 bExitValue ) +{ + // yes + if( bExitValue == MSG_BOX_RETURN_YES ) + { + if( sSelectedMilitiaTown != BLANK_SECTOR ) + { + // HEADROCK HAM 3.6: Adjust loyalty based on how many militia were disbanded, compared to + // how many can be trained in a single session (max). + UINT32 uiMilitiaToDisband = sGreensOnCursor + (2*sRegularsOnCursor) + (3*sElitesOnCursor); + UINT32 uiTownLoyaltyBonus = (LOYALTY_BONUS_FOR_TOWN_TRAINING * uiMilitiaToDisband) / gGameExternalOptions.iTrainingSquadSize; + // loyalty in this town increases a bit because we obviously care about them... + DecrementTownLoyalty( (UINT8)sSelectedMilitiaTown, uiTownLoyaltyBonus ); + } + + // zero out numbers on the cursor + sGreensOnCursor = 0; + sRegularsOnCursor = 0; + sElitesOnCursor = 0; + + // reset fact we are in the box + sSelectedMilitiaTown = 0; + fMapPanelDirty = TRUE; + // Go ahead and reset the militia in the selected sector + ResetMilitia(); + } + else if( bExitValue == MSG_BOX_RETURN_NO ) + { + return; + } + + return; +} \ No newline at end of file diff --git a/Strategic/Map Screen Interface TownMine Info.cpp b/Strategic/Map Screen Interface TownMine Info.cpp index b6f0a658..68d510a5 100644 --- a/Strategic/Map Screen Interface TownMine Info.cpp +++ b/Strategic/Map Screen Interface TownMine Info.cpp @@ -340,11 +340,8 @@ void AddTextToTownBox( void ) AddSecondColumnMonoString( &hStringHandle, wString ); // main facilities - swprintf( wString, L"%s:", pwTownInfoStrings[ 8 ] ); - AddMonoString( &hStringHandle, wString ); - wcscpy(wString, L""); - GetSectorFacilitiesFlags( bCurrentTownMineSectorX, bCurrentTownMineSectorY, wString ); - AddSecondColumnMonoString( &hStringHandle, wString ); + // HEADROCK HAM 3.6: This function now does all the work of assembling a facility entry. + AddFacilitiesToBox( bCurrentTownMineSectorX, bCurrentTownMineSectorY, &hStringHandle, TRUE ); // the concept of control is only meaningful in sectors where militia can be trained if ( MilitiaTrainingAllowedInSector( bCurrentTownMineSectorX, bCurrentTownMineSectorY, 0 ) ) @@ -551,6 +548,9 @@ void AddTextToBlankSectorBox( void ) // sector AddSectorToBox(); + + // HEADROCK HAM 3.6: Facilities + AddFacilitiesToBox( bCurrentTownMineSectorX, bCurrentTownMineSectorY, &hStringHandle, FALSE ); } @@ -639,12 +639,64 @@ void AddCommonInfoToBox(void) AddSecondColumnMonoString( &hStringHandle, wString ); } + // HEADROCK HAM 3.6: Only show these for sectors that have a training facility + BOOLEAN fMilitiaTrainingAllowed = FALSE; + BOOLEAN fMobileTrainingAllowed = FALSE; // percentage of current militia squad training completed swprintf( wString, L"%s:", pwTownInfoStrings[ 10 ] ); AddMonoString( &hStringHandle, wString ); - swprintf( wString, L"%d%%%%", SectorInfo[ SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ].ubMilitiaTrainingPercentDone ); - AddSecondColumnMonoString( &hStringHandle, wString ); + + // Sector contains Militia training facility? + for (UINT8 ubCounter = 0; ubCounter < MAX_NUM_FACILITY_TYPES; ubCounter++) + { + if (gFacilityLocations[SECTOR(bCurrentTownMineSectorX, bCurrentTownMineSectorY)][ubCounter].fFacilityHere) + { + if (gFacilityTypes[ubCounter].ubMilitiaTrainersAllowed) + { + fMilitiaTrainingAllowed = TRUE; + } + } + } + if (fMilitiaTrainingAllowed) + { + // Show percent completed + swprintf( wString, L"%d%%%%", SectorInfo[ SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ].ubMilitiaTrainingPercentDone ); + AddSecondColumnMonoString( &hStringHandle, wString ); + } + else + { + // Show N/A + AddSecondColumnMonoString( &hStringHandle, New113HAMMessage[19] ); + } + + // HEADROCK HAM 3.6: percentage of current Mobile Militia squad training completed + swprintf( wString, L"%s:", pwTownInfoStrings[ 12 ] ); + AddMonoString( &hStringHandle, wString ); + + // Sector contains Mobile training facility? + for (UINT8 ubCounter = 0; ubCounter < MAX_NUM_FACILITY_TYPES; ubCounter++) + { + if (gFacilityLocations[SECTOR(bCurrentTownMineSectorX, bCurrentTownMineSectorY)][ubCounter].fFacilityHere) + { + if (gFacilityTypes[ubCounter].ubMobileMilitiaTrainersAllowed) + { + fMobileTrainingAllowed = TRUE; + } + } + } + + if (fMobileTrainingAllowed) + { + // Show percentage completed + swprintf( wString, L"%d%%%%", SectorInfo[ SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ) ].ubMobileMilitiaTrainingPercentDone ); + AddSecondColumnMonoString( &hStringHandle, wString ); + } + else + { + // Show N/A + AddSecondColumnMonoString( &hStringHandle, New113HAMMessage[19] ); + } } diff --git a/Strategic/Map Screen Interface.cpp b/Strategic/Map Screen Interface.cpp index 7da91298..aee50c25 100644 --- a/Strategic/Map Screen Interface.cpp +++ b/Strategic/Map Screen Interface.cpp @@ -313,6 +313,8 @@ BOOLEAN fShowContractMenu = FALSE; BOOLEAN fShowRemoveMenu = FALSE; BOOLEAN fShowMilitiaControlMenu = FALSE; //lal //BOOLEAN fShowTalkToAllMenu = FALSE;//lal +BOOLEAN fShowFacilityMenu = FALSE; // HEADROCK HAM 3.6: Facility Menu +BOOLEAN fShowFacilityAssignmentMenu = FALSE; // HEADROCK HAM 3.6: Facility Sub-menu BOOLEAN fRebuildMoveBox = FALSE; @@ -325,6 +327,12 @@ SGPRect TrainDimensions={0,0,100,95}; SGPPoint TrainPosition={160,150}; SGPRect VehicleDimensions={0,0,80,60}; SGPPoint VehiclePosition={160,150}; +// HEADROCK HAM 3.6: Facility Menu Position/Size: +SGPRect FacilityDimensions={0,0,80,60}; +SGPPoint FacilityPosition={160,150}; +// HEADROCK HAM 3.6: Facility Sub-menu Position/Size: +SGPRect FacilityAssignmentDimensions={0,0,80,60}; +SGPPoint FacilityAssignmentPosition={220,150}; SGPPoint RepairPosition={160,150}; SGPRect RepairDimensions={0,0,80,80}; @@ -350,6 +358,8 @@ SGPPoint OrigAssignmentPosition = { 120, 150 }; SGPPoint OrigTrainPosition={160,150}; SGPPoint OrigVehiclePosition={160,150}; SGPPoint OrigMilitiaControlPosition = { 120, 150 }; //lal +SGPPoint OrigFacilityPosition = { 160, 150 }; // HEADROCK HAM 3.6 +SGPPoint OrigFacilityAssignmentPosition = { 220,150 }; // HEADROCK HAM 3.6 //SGPPoint OrigTalkToAllPosition = { 160, 150 }; //extern BOOLEAN fMapExitDueToMessageBox; @@ -542,7 +552,7 @@ BOOLEAN MultipleCharacterListEntriesSelected( void ) -void ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( INT8 bAssignment ) +void ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( UINT8 ubMilitiaType ) { INT32 iCounter = 0; SOLDIERTYPE *pSoldier = NULL; @@ -563,18 +573,31 @@ void ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( INT8 bAssignme continue; } - if( pSoldier->bAssignment == TRAIN_TOWN ) + if (ubMilitiaType == TOWN_MILITIA) { - if ( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + if( pSoldier->bAssignment == TRAIN_TOWN ) { - ResumeOldAssignment( pSoldier ); + if ( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + { + ResumeOldAssignment( pSoldier ); + } + } + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + if( pSoldier->bAssignment == TRAIN_MOBILE ) + { + if ( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMobileMilitiaTrainingPaid == FALSE ) + { + ResumeOldAssignment( pSoldier ); + } } } } } - -void ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( INT16 sSectorX, INT16 sSectorY ) +// HEADROCK HAM 3.6: Added argument for Militia Type +void ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( INT16 sSectorX, INT16 sSectorY, UINT8 ubMilitiaType ) { INT32 iCounter = 0; SOLDIERTYPE *pSoldier = NULL; @@ -595,11 +618,24 @@ void ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( INT16 sSectorX, continue; } - if( pSoldier->bAssignment == TRAIN_TOWN ) + if( ubMilitiaType == TOWN_MILITIA ) { - if( ( pSoldier->sSectorX == sSectorX ) && ( pSoldier->sSectorY == sSectorY ) && ( pSoldier->bSectorZ == 0 ) ) + if( pSoldier->bAssignment == TRAIN_TOWN ) { - ResumeOldAssignment( pSoldier ); + if( ( pSoldier->sSectorX == sSectorX ) && ( pSoldier->sSectorY == sSectorY ) && ( pSoldier->bSectorZ == 0 ) ) + { + ResumeOldAssignment( pSoldier ); + } + } + } + else if (ubMilitiaType == MOBILE_MILITIA ) + { + if( pSoldier->bAssignment == TRAIN_MOBILE ) + { + if( ( pSoldier->sSectorX == sSectorX ) && ( pSoldier->sSectorY == sSectorY ) && ( pSoldier->bSectorZ == 0 ) ) + { + ResumeOldAssignment( pSoldier ); + } } } } @@ -890,6 +926,7 @@ void RestoreBackgroundForAssignmentGlowRegionList( void ) // restore background RestoreExternBackgroundRect( 66, Y_START - 1, 118 + 1 - 67, yHeight ); + // ARM: not good enough! must reblit the whole panel to erase glow chunk restored by help text disappearing!!! fTeamPanelDirty = TRUE; @@ -2285,7 +2322,24 @@ void UpdateMapScreenAssignmentPositions( void ) SetBoxPosition( ghRepairBox, pPoint ); } + // HEADROCK HAM 3.6: Facility Menu + if( fShowFacilityMenu ) + { + GetBoxPosition( ghFacilityBox, &pPoint); + pPoint.iY = giBoxY + ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY; + SetBoxPosition( ghFacilityBox, pPoint ); + } + + // HEADROCK HAM 3.6: Facility Sub-menu + if( fShowFacilityAssignmentMenu ) + { + GetBoxPosition( ghFacilityAssignmentBox, &pPoint); + + pPoint.iY = giBoxY + ( GetFontHeight( MAP_SCREEN_FONT ) + 2 ) * ASSIGN_MENU_FACILITY; + + SetBoxPosition( ghFacilityAssignmentBox, pPoint ); + } return; } @@ -5655,7 +5709,8 @@ BOOLEAN HandleTimeCompressWithTeamJackedInAndGearedToGo( void ) if (!is_networked) // select starting sector (A9 - Omerta) - ChangeSelectedMapSector( startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Externalized. + ChangeSelectedMapSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); if (is_networked) { @@ -5671,22 +5726,30 @@ BOOLEAN HandleTimeCompressWithTeamJackedInAndGearedToGo( void ) else { // load starting sector - if ( !SetCurrentWorldSector( startingX, startingY, startingZ ) ) + // Externalized + if ( !SetCurrentWorldSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ) ) { return( FALSE ); } - gubPBSectorX = startingX; - gubPBSectorY = startingY; + gubPBSectorX = gGameExternalOptions.ubDefaultArrivalSectorX; + gubPBSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; } - + gubPBSectorZ = 0; //Setup variables in the PBI for this first battle. We need to support the //non-persistant PBI in case the user goes to mapscreen. - gfBlitBattleSectorLocator = TRUE; - - gubPBSectorZ = 0; - gubEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE; + // HEADROCK HAM 3.5: Arrival sector externalized. Fix for entering non-hostile sector/ + if ( NumEnemiesInAnySector( gubPBSectorX, gubPBSectorY, 0 ) > 0 ) + { + gfBlitBattleSectorLocator = TRUE; + gubEnemyEncounterCode = ENTERING_ENEMY_SECTOR_CODE; + } + else + { + gfBlitBattleSectorLocator = FALSE; + gubEnemyEncounterCode = NO_ENCOUNTER_CODE; + } InitHelicopterEntranceByMercs( ); diff --git a/Strategic/Map Screen Interface.h b/Strategic/Map Screen Interface.h index b8991cf6..13cdf9cb 100644 --- a/Strategic/Map Screen Interface.h +++ b/Strategic/Map Screen Interface.h @@ -113,15 +113,17 @@ enum { ASSIGN_MENU_VEHICLE, ASSIGN_MENU_REPAIR, ASSIGN_MENU_TRAIN, + ASSIGN_MENU_FACILITY, // HEAROCK HAM 3.6: Facility List menu ASSIGN_MENU_CANCEL, MAX_ASSIGN_STRING_COUNT, }; - +// HEADROCK HAM 3.6: Added new option to train Mobile Militia separately. // training assignment menu defines enum { TRAIN_MENU_SELF, TRAIN_MENU_TOWN, + TRAIN_MENU_MOBILE, TRAIN_MENU_TEAMMATES, TRAIN_MENU_TRAIN_BY_OTHER, TRAIN_MENU_CANCEL, @@ -202,6 +204,12 @@ enum{ STOP_YELLOW_SECTOR_LOCATOR, }; +// HEADROCK HAM 3.6: Enums for Militia Training Types +enum{ + TOWN_MILITIA = 0, + MOBILE_MILITIA, + NUM_MILITIA_TRAINING_TYPES +}; // dimensions and offset for merc update box #define UPDATE_MERC_FACE_X_WIDTH 50 @@ -254,6 +262,8 @@ extern BOOLEAN fShowContractMenu ; extern BOOLEAN fShowRemoveMenu ; extern BOOLEAN fShowMilitiaControlMenu ; //lal //extern BOOLEAN fShowTalkToAllMenu ; +extern BOOLEAN fShowFacilityMenu; // HEADROCK HAM 3.6 +extern BOOLEAN fShowFacilityAssignmentMenu; // HEADROCK HAM 3.6 extern BOOLEAN fFirstTimeInMapScreen; extern BOOLEAN fLockOutMapScreenInterface; @@ -299,6 +309,11 @@ extern SGPRect AssignmentDimensions ; extern SGPPoint AssignmentPosition ; extern SGPPoint SquadPosition ; extern SGPRect SquadDimensions ; +// HEADROCK HAM 3.6: Facility Menu variables +extern SGPPoint FacilityPosition; +extern SGPRect FacilityDimensions; +extern SGPPoint FacilityAssignmentPosition; +extern SGPRect FacilityAssignmentDimensions; extern SGPPoint RepairPosition; extern SGPRect RepairDimensions; @@ -316,6 +331,8 @@ extern SGPPoint OrigAssignmentPosition ; extern SGPPoint OrigTrainPosition; extern SGPPoint OrigVehiclePosition; extern SGPPoint OrigMilitiaControlPosition ; //lal +extern SGPPoint OrigFacilityPosition; // HEADROCK HAM 3.6: Facility Menu +extern SGPPoint OrigFacilityAssignmentPosition; // HEADROCK HAM 3.6 //extern SGPPoint OrigTalkToAllPosition ; // disble team info panel due to showing of battle roster @@ -374,7 +391,8 @@ BOOLEAN MultipleCharacterListEntriesSelected( void ); void ToggleEntryInSelectedList( INT8 bEntry ); // reset assignments for mercs on selected list who have this assignment -void ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( INT8 bAssignment ); +// HEADROCK HAM 3.6: Argument was unused... adding my own. +void ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( UINT8 ubMilitiaType ); /* // plot path for selected character list @@ -553,7 +571,7 @@ BOOLEAN SetUpFastHelpListRegions( INT32 iXPosition[], INT32 iYPosition[], INT32 void InitializeMovingLists( void ); // reset assignment for mercs trainign militia in this sector -void ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( INT16 sSectorX, INT16 sSectorY ); +void ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( INT16 sSectorX, INT16 sSectorY, UINT8 ubMilitiaType ); // the sector move box diff --git a/Strategic/MilitiaSquads.cpp b/Strategic/MilitiaSquads.cpp index 15fe631e..d61de835 100644 --- a/Strategic/MilitiaSquads.cpp +++ b/Strategic/MilitiaSquads.cpp @@ -64,6 +64,7 @@ #define DIR_SOUTH 2 #define DIR_WEST 3 + INT32 iRestrictedSectorArraySize; UINT32 gRestrictMilitia[256]; // HEADROCK HAM B1: Alternate array keeps track of dynamically unrestricted sectors @@ -71,6 +72,8 @@ BOOLEAN gDynamicRestrictMilitia[ 256 ]; // HEADROCK HAM B1: Function that dynamically unrestricts sectors as we take over towns. extern void AdjustRoamingRestrictions(); +DYNAMICRESTRICTIONS gDynamicRestrictions[5001]; + UINT8 gpAttackDirs[5][4]; // 0. Green Militia 1. Regular Militia 2. Elite Militia 3. Insertion code UINT8 guiDirNumber = 0; BOOLEAN gfMSBattle = FALSE; @@ -142,62 +145,285 @@ UINT8 CountMilitia(SECTORINFO *pSectorInfo) pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]; } + // Creates militia at destination sector. The type and amount of militia depends on current sector's miltia type and amount -void GenerateMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY ) +// HEADROCK HAM 3.4: Added Leadership argument. +void GenerateMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY, UINT8 ubBestLeadership ) { - SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); + ////////////////////////////////////////////////////////////////////////////////////// + // HEADROCK HAM 3.3: Introduced considerable changes here on top of HAM 2 code. You can replace + // the entire function with this new code. + // Mostly, the changes are geared towards variable militia QUANTITY due to variable trainer Leadership. + // Also introduced minimum required leadership for training mobile militia, and minimum required + // leadership for training a full squad. + ////////////////////////////////////////////////////////////////////////////////////// + + SECTORINFO *pSourceSector = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); + SECTORINFO *pTargetSector = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] ); //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; + UINT16 usTotalMilitiaAtSource = pSourceSector->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] + pSourceSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] + pSourceSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + UINT16 usTotalMilitiaAtTarget = pTargetSector->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] + pTargetSector->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] + pTargetSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + // Desired number of Greens, Regulars and Elites to create. + UINT8 ubTargetGreen, ubTargetRegular, ubTargetElite; + // Upgrade points for replacing militia with better ones. + UINT8 ubUpgradePoints; + // Calculate default number of militia to place in Target Sector. + UINT8 ubMilitiaToTrain = gGameExternalOptions.iMaxMilitiaPerSector / gGameExternalOptions.guiDivOfOriginalMilitia; + // Percentage of troops that should be of any one type: + UINT8 ubTargetElitePercent = 0; + UINT8 ubTargetRegularPercent = 0; + UINT8 ubTempLeadership = ubBestLeadership; - if (gGameExternalOptions.fDiverseRoamingMilitiaGroups) + // Does trainer have enough leadership to train a squad? + if ( ubBestLeadership < gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia ) { - 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); + // This is punishment, basically. The merc still gets the benefit of improving his leadership through the training + // session, but a lot of money has basically gone to waste. This is something to watch out for, shall we say? + ubMilitiaToTrain = 0; } - 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); + // HEADROCK HAM 3.6: New function to do Quantity Calculation based on leadership + ubMilitiaToTrain = CalcNumMilitiaTrained(ubBestLeadership, TRUE); } - pSectorInfo = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] ); + // HEADROCK HAM 3.4: Composition of new Mobile Militia groups is now dictated by two INI settings controlling + // the percentage of Elites and Regulars within the group. If the percentage for either is above 0, at least + // one militia of that type will be created every time. Green militia are created based on whatever remains. - while( CountMilitia(pSectorInfo) > gGameExternalOptions.guiMaxMilitiaSquadSize ) + // HEADROCK HAM 3.5: Base percentages off the trainer's Leadership... + if ( gGameExternalOptions.fLeadershipAffectsMobileMilitiaQuality && + (ubBestLeadership >= gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia) ) { - if(pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]) + // Find out how far between the minimum and maximum our character is. + INT8 bRange = gGameExternalOptions.ubReqLeadershipForFullMobileTraining - gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia; + ubBestLeadership = __min(ubBestLeadership, gGameExternalOptions.ubReqLeadershipForFullMobileTraining); + ubBestLeadership -= gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia; + + UINT8 ubBestLeadershipPercentage = (ubBestLeadership * 100) / bRange; + + // There are five "grades", which in practice actually give a smooth transition from All-Green to All-Elite. + // Please note that if elite training is disabled, all elites will later be converted to Regulars. In that case, + // a character needs to have at least 60% of the total leadership range to train a full squad of regulars. + if (ubBestLeadershipPercentage >= 0 && ubBestLeadershipPercentage < 20) // First step, Greens { - --pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]; + ubTargetElitePercent = 0; // No elites + ubTargetRegularPercent = 0; // No regulars + // All green. } - else if(pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]) + else if (ubBestLeadershipPercentage >= 20 && ubBestLeadershipPercentage < 40) { - --pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]; + ubTargetElitePercent = 0; // No elites + ubTargetRegularPercent = __max(1,(((ubBestLeadershipPercentage-19) * 50) / 20)); // Up to 50% regulars + // Green is 100%-ubTargetRegularPercent. So it's between 0/0/100 and 0/50/50 } - else if(pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]) + else if (ubBestLeadershipPercentage >= 40 && ubBestLeadershipPercentage < 60) { - --pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]; + ubTargetElitePercent = __max(1,(((ubBestLeadershipPercentage-39) * 33) / 20)); // Up to 33% elites + ubTargetRegularPercent = __max(1,(100 - ubTargetElitePercent)/2); // Half of what's left is regulars + // And half is green. This gives anywhere between 1/49/49 and 33/33/33. + } + else if (ubBestLeadershipPercentage >= 60 && ubBestLeadershipPercentage < 80) + { + UINT8 ubTargetGreenPercent = __max(1, (((20 - (ubBestLeadershipPercentage-60))*33) / 20)); + ubTargetElitePercent = (100-ubTargetGreenPercent)/2; + ubTargetRegularPercent = ubTargetElitePercent; + // Green percent drops as your leadership goes up, while Elite and Regular are equal. + // This gives anywhere between 33/33/33 and 49/49/1 + } + else if (ubBestLeadershipPercentage >= 80 && ubBestLeadershipPercentage < 100) + { + ubTargetElitePercent = 50 + (((ubBestLeadershipPercentage-80) * 50) / 20); + ubTargetRegularPercent = 100 - ubTargetElitePercent; + // Elites increase from 50% to 100% while regulars drop to 0%. No greens. + } + else // Best possible leadership = Full Elites + { + ubTargetElitePercent = 100; + ubTargetRegularPercent = 0; + // All elites. + } + } + else + // INI-defined percentages + { + ubTargetElitePercent = gGameExternalOptions.ubPercentRoamingMilitiaElites; + ubTargetRegularPercent = gGameExternalOptions.ubPercentRoamingMilitiaRegulars; + } + + ubTargetElite = (ubMilitiaToTrain * ubTargetElitePercent) / 100; + if (ubTargetElitePercent) // If percentage is positive + ubTargetElite = __max(1, ubTargetElite); // Create at least one. + + ubTargetRegular = (ubMilitiaToTrain * ubTargetRegularPercent) / 100; + if (ubTargetRegularPercent) // If percentage is positive + ubTargetRegular = __max(1, ubTargetRegular); // Create at least one. + + // Greens are created based on what's left of the MilitiaToTrain that has not already + // been allocated to Regulars or Elites. + ubTargetGreen = ubMilitiaToTrain - (ubTargetRegular + ubTargetElite); + if (ubTargetRegularPercent+ubTargetElitePercent < 100) // If there's also a remainder, percentage-wise + ubTargetGreen = __max(1, ubTargetGreen); // Create at least one. + + // Is Elite Militia allowed at all? + if (!gGameExternalOptions.gfTrainVeteranMilitia || + GetWorldDay( ) < gGameExternalOptions.guiTrainVeteranMilitiaDelay) + { + // Convert all Elites to Regulars + ubTargetRegular += ubTargetElite; + ubTargetElite = 0; + } + + // Make sure we're not training more militia than we're allowed. This kicks in when the number of militia + // to train is lower than 3, for any reason (particularly, not meeting the minimum required leadership). + while (ubTargetRegular+ubTargetElite+ubTargetGreen > ubMilitiaToTrain) + { + // Reduce target elite count first. + if (ubTargetElite > 0) + { + ubTargetElite--; + continue; + } + // If no more elites but still over the allowed value, then reduce one regular. + if (ubTargetRegular > 0) + { + ubTargetRegular--; + continue; + } + // No elites/regulars - reduce target green! + ubTargetGreen--; + } + + while (ubMilitiaToTrain > 0) + { + + //////////////////////////////// + // Create Heterogenous Group + // + // As of HAM 3.4, homogenous groups are not treated any differently than these. + //////////////////////////////// + + // Do we have room in the target sector? + if ( usTotalMilitiaAtTarget < gGameExternalOptions.iMaxMilitiaPerSector ) + { + if (ubTargetElite > 0) + { + // Add elite. + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, 1); + ubTargetElite--; + ubMilitiaToTrain--; + } + else if (ubTargetRegular > 0) + { + // Add regular. + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, 1); + ubTargetRegular--; + ubMilitiaToTrain--; + } + else if (ubTargetGreen > 0) + { + // Add green. + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, 1); + ubTargetGreen--; + ubMilitiaToTrain--; + } + else + { + // No one to add? Break the cycle. + ubMilitiaToTrain = 0; + } + } + + // Full militia group? See if you can upgrade some. + else + { + // Are we dealing with a full-size militia group that ISN'T full-quality yet? + if (pTargetSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < usTotalMilitiaAtTarget && + pTargetSector->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < gGameExternalOptions.iMaxMilitiaPerSector) // failsafe + { + // Have we got any Elites to add? + if (ubTargetElite > 0) + { + // Add elite. This will effectively replace one lower class militia + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, 1); + ubTargetElite--; + ubMilitiaToTrain--; + } + else if (ubTargetRegular > 0 && // Got a regular + (!gGameExternalOptions.gfTrainVeteranMilitia || // Not allowed to train Elites + GetWorldDay( ) < gGameExternalOptions.guiTrainVeteranMilitiaDelay )) // Or not YET allowed to train elites + { + // Add a regular. This will effectively replace one Green militia + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, 1); + ubTargetElite--; + ubMilitiaToTrain--; + } + // Else, we've got more men to train but no room for them. In this case, upgrade existing + // militia to the next class, using our remainder men as "upgrade points". + else + { + // Calculate upgrade points. 1 point per Green, 2 per Regular. + ubUpgradePoints = ubTargetGreen + (2 * ubTargetRegular); + + // Is Elite training allowed? + if ( gGameExternalOptions.gfTrainVeteranMilitia && // Elite training allowed + (GetWorldDay( ) >= gGameExternalOptions.guiTrainVeteranMilitiaDelay) // Elite training allowed (time based) + && ubUpgradePoints >= 2) // Enough upgrade points to create an elite + { + // Add one elite + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, 1); + if (ubTargetRegular > 0) + { + // Comes at a cost of one regular that we were supposed to train. + ubTargetRegular--; + ubMilitiaToTrain--; + } + else + { + // No regulars to train? Remove two greens instead. + ubTargetGreen -= 2; + ubMilitiaToTrain -= 2; + } + } + // Else elite training was not allowed or we simply did not have enough points to add an elite + else if (ubUpgradePoints >= 1) + { + // Add one regular instead of a green. + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, 1); + ubTargetGreen--; + ubMilitiaToTrain--; + } + else + { + // No more men to train. Break the cycle. + ubMilitiaToTrain = 0; + } + } + } + else + { + // Sector is full-sized and full-quality. Break the cycle. + ubMilitiaToTrain = 0; + } + } + } + + // This reduces the group back to "maximum" size. It starts by eliminating extra greens, then regulars, then elites. + // That produces a group of max size, with only the best troops remaining. + while( CountMilitia(pTargetSector) > gGameExternalOptions.iMaxMilitiaPerSector ) + { + if(pTargetSector->ubNumberOfCivsAtLevel[GREEN_MILITIA]) + { + --pTargetSector->ubNumberOfCivsAtLevel[GREEN_MILITIA]; + } + else if(pTargetSector->ubNumberOfCivsAtLevel[REGULAR_MILITIA]) + { + --pTargetSector->ubNumberOfCivsAtLevel[REGULAR_MILITIA]; + } + else if(pTargetSector->ubNumberOfCivsAtLevel[ELITE_MILITIA]) + { + --pTargetSector->ubNumberOfCivsAtLevel[ELITE_MILITIA]; } } @@ -220,14 +446,79 @@ void MoveMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY, BOOL UINT8 bTotalGreens = 0, bTotalRegulars = 0, bTotalElites = 0; UINT8 bTotalGreensPercent = 0, bTotalRegularsPercent = 0, bTotalElitesPercent = 0; INT8 bNewSourceGroupSize = 0, bNewDestGroupSize = 0, bGroupSizeRatio = 0; + UINT8 ubChanceToSpreadOut; + + ////////////////////////////////////////////////////////// + // We begin by determining the group's chance to "spread out" into the next sector rather + // than moving in en-masse. This is mainly affected by the presence of enemies. - // HEADROCK HAM B2.8 - // There are now several methods of militia join-up. - if (PreRandom(100) < gGameExternalOptions.ubRoamingMilitiaSpreadsOutChance) + // Is the target a city or SAM site? + if (IsThisSectorASAMSector( sTMapX, sTMapY, 0 ) || + ( GetTownIdForSector( sTMapX, sTMapY ) != BLANK_SECTOR && + gfMilitiaAllowedInTown[GetTownIdForSector( sTMapX, sTMapY )] )) + { + // Mobile->Town movement is always en-masse. We do not want to reduce the number + // of Garrison troops in the town! They can only be increased. + ubChanceToSpreadOut = 0; + } + else + { + // Wilderness->Wilderness movement. Calculate Spread Out chance based on threats. + + if (NumEnemiesInFiveSectors( sMapX, sMapY ) == 0 && + NumEnemiesInFiveSectors( sTMapX, sTMapY ) > 0 ) + { + // Target sector is threatened, while source sector is not. + // Target sector gets all the militia it can, in preperation for a possible enemy attack. + ubChanceToSpreadOut = 0; + } + else if (NumEnemiesInFiveSectors( sMapX, sMapY ) > 0 && + NumEnemiesInFiveSectors( sTMapX, sTMapY ) > 0 ) + { + // Both sectors threatened. Militia will spread out between them, to maximize defensive capability. + ubChanceToSpreadOut = 100; + } + + else if (NumEnemiesInFiveSectors( sMapX, sMapY ) > 0 && + NumEnemiesInFiveSectors( sTMapX, sTMapY ) == 0 ) + { + // Source sector is threatened, while destination is not. The group does not + // move at all, as it is already in a good position to defend. + return; + } + else + { + /////////////////////////////////////////////////////// + // Both sectors not threatened. + // Alter spreadout chance based on number of troops in Source and destination sectors. + + if ( CountMilitia(pSectorInfo) >= gGameExternalOptions.iMaxMilitiaPerSector && + CountMilitia(pTSectorInfo) == 0 ) + { + // Source group is full, target sector is empty. The source group gets 30% chance to + // spread out to that empty sector. + ubChanceToSpreadOut = 30; + } + else if ( CountMilitia(pSectorInfo) && CountMilitia(pTSectorInfo) ) + { + // Source and destination groups both exist. 50% chance to spread out evenly between them. + ubChanceToSpreadOut = 50; + } + else + { + // Target sector is empty, source group is not full. Do not spread out. + ubChanceToSpreadOut = 0; + } + } + } + + // Should we spread out into the target sector, or move en-masse? + if (PreRandom(100) < ubChanceToSpreadOut) { - // Militia will attempt to spread out, averaging the size and composition of both groups. if ( CountMilitia(pSectorInfo) && CountMilitia(pTSectorInfo) ) { + // Source and Target sectors both have groups in them. Source group will attempt to spread out, + // averaging the size and composition of both groups. bGreensSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; bGreensDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; bRegularsSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; @@ -267,95 +558,125 @@ void MoveMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY, BOOL } 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 ] ); + // Target sector is empty. Source group will split up into two equally-sized groups. + bGreensSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + bGreensDestTeam = bGreensSourceTeam / 2; + + bRegularsSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + bRegularsDestTeam = bRegularsSourceTeam / 2; + + bElitesSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + bElitesDestTeam = bElitesSourceTeam / 2; + + // Add half team to target sector + StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, bGreensDestTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, bRegularsDestTeam ); + StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, bElitesDestTeam ); - 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 ] ); + // Remove half team from source sector + StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, bGreensDestTeam ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, bRegularsDestTeam ); + StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, bElitesDestTeam ); + + // Units that move into an empty square do not get to move again. + AddToBlockMoveList( sTMapX, sTMapY ); } } else { - if ( gGameExternalOptions.fDiverseRoamingMilitiaGroups ) + // Source team moves En-Masse to target sector. + + bNewDestGroupSize = __min((CountMilitia(pTSectorInfo) + CountMilitia(pSectorInfo)), (UINT8)gGameExternalOptions.iMaxMilitiaPerSector); + bNewSourceGroupSize = __max(0,(CountMilitia(pTSectorInfo) + CountMilitia(pSectorInfo)) - gGameExternalOptions.iMaxMilitiaPerSector); + + // If there are still going to be two teams after the transfer + if ( bNewSourceGroupSize > 0 ) { - // 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); + // Make sure that the two teams end up with the same ratio of Greens/Regulars/Elites + 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 ]; - // If there are still going to be two teams after the transfer - if ( bNewSourceGroupSize > 0 ) + 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; + + // Correct fractions to full squad size at the target. + while (bGreensSourceTeam + bRegularsSourceTeam + bElitesSourceTeam > 3 && + bGreensDestTeam + bRegularsDestTeam + bElitesDestTeam < bNewDestGroupSize) { - - 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 ] ); + if (bGreensSourceTeam > 0) + { + // Move a green to the dest team + bGreensSourceTeam--; + bGreensDestTeam++; + continue; + } + if (bRegularsSourceTeam > 0) + { + // Move a regular to the dest team + bRegularsSourceTeam--; + bRegularsDestTeam++; + continue; + } + if (bElitesSourceTeam > 0) + { + // Move an elite to the dest team + bElitesSourceTeam--; + bElitesDestTeam++; + continue; + } } + + // 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 { - // 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 ] ); + // Entire group moves from Source to Target, leaving no one behind. + 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 ] ); + 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 ] ); + + // Block target group from moving. It has just assimilated another group. + AddToBlockMoveList( sTMapX, sTMapY ); } } - while( !fAlternativeMax && CountMilitia(pTSectorInfo) > gGameExternalOptions.guiMaxMilitiaSquadSize || - fAlternativeMax && CountMilitia(pTSectorInfo) > gGameExternalOptions.guiMaxMilitiaSquadSizeBattle ) + while( !fAlternativeMax && CountMilitia(pTSectorInfo) > gGameExternalOptions.iMaxMilitiaPerSector ) { if(pTSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]) { @@ -474,7 +795,7 @@ UINT16 CountDirectionRating( INT16 sMapX, INT16 sMapY, UINT8 uiDir ) INT32 iRes = BASE_DIR_PRIORITY + CountDirectionEnemyRating( sMapX, sMapY, uiDir ); INT16 sDMapX, sDMapY; - INT32 iDiff; + FLOAT iDiff; switch(uiDir) { @@ -498,27 +819,109 @@ UINT16 CountDirectionRating( INT16 sMapX, INT16 sMapY, UINT8 uiDir ) return iRes; } - if( CountAllMilitiaInSector( sDMapX, sDMapY ) && - (UINT32)( CountAllMilitiaInSector( sDMapX, sDMapY ) + CountAllMilitiaInSector( sMapX, sMapY ) ) <= gGameExternalOptions.guiMaxMilitiaSquadSize ) + UINT8 ubMaxSquadSize = gGameExternalOptions.iMaxMilitiaPerSector; + + UINT8 ubSourceGreens = SectorInfo[SECTOR(sMapX, sMapY)].ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + UINT8 ubSourceRegulars = SectorInfo[SECTOR(sMapX, sMapY)].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + UINT8 ubSourceElites = SectorInfo[SECTOR(sMapX, sMapY)].ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + UINT8 ubTargetGreens = SectorInfo[SECTOR(sDMapX, sDMapY)].ubNumberOfCivsAtLevel[ GREEN_MILITIA ]; + UINT8 ubTargetRegulars = SectorInfo[SECTOR(sDMapX, sDMapY)].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]; + UINT8 ubTargetElites = SectorInfo[SECTOR(sDMapX, sDMapY)].ubNumberOfCivsAtLevel[ ELITE_MILITIA ]; + + UINT8 ubSourceMilitia = ubSourceGreens + ubSourceRegulars + ubSourceElites; + UINT8 ubTargetMilitia = ubTargetGreens + ubTargetRegulars + ubTargetElites; + + UINT16 usSourceEnemiesInFiveSectors = NumEnemiesInFiveSectors( sMapX, sMapY ); + UINT16 usTargetEnemiesInFiveSectors = NumEnemiesInFiveSectors( sDMapX, sDMapY ); + UINT8 ubTargetEnemies = NumEnemiesInSector( sDMapX, sDMapY ); + + UINT16 usSourceMilitiaInFiveSectors = CountAllMilitiaInFiveSectors( sMapX, sMapY ); + UINT16 usTargetMilitiaInFiveSectors = CountAllMilitiaInFiveSectors( sDMapX, sDMapY ); + + UINT8 bSourceTownId = GetTownIdForSector( sMapX, sMapY ); + UINT8 bTargetTownId = GetTownIdForSector( sDMapX, sDMapY ); + BOOLEAN fTargetSAMSite = IsThisSectorASAMSector( sDMapX, sDMapY, 0 ); + + // Is target sector a city/SAM where militia can be recruited? + if ( (bTargetTownId != BLANK_SECTOR && gfMilitiaAllowedInTown[bTargetTownId]) + || fTargetSAMSite ) + { + // Does roaming group have anything to contribute to this town/SAM? + if ( ubTargetElites >= ubMaxSquadSize || // Target sector full of elites + (ubTargetRegulars+ubTargetElites >= ubMaxSquadSize && ubSourceElites == 0) || // Target sector full of Elite+Regular, and source has no elites + (ubTargetMilitia >= ubMaxSquadSize && ubSourceRegulars+ubSourceElites == 0) ) // Target sector is full of anything, Souce sector has no Elites or Regulars + { + // Ignore sector. + return 0; + } + } + + // Else, target sector is a free-roaming sector. + else + { + // Are both squads full of militia? + if (ubSourceMilitia >= ubMaxSquadSize && ubTargetMilitia >= ubMaxSquadSize) + { + // Are both squads the same composition? + if ( ubSourceGreens == ubTargetGreens && + ubSourceRegulars == ubTargetRegulars && + ubSourceElites == ubTargetElites ) + { + // No reason to merge or average the squads, so ignore this sector. + return 0; + } + } + + // Does source group have anything to offer to the target group? + else if (ubTargetElites >= ubMaxSquadSize || // Target full of elites already + ( ubTargetRegulars+ubTargetElites >= ubMaxSquadSize && ubSourceElites == 0 ) || // Target full of regulars and elites, source group has no elites + ( ubTargetMilitia >= ubMaxSquadSize && ubSourceElites + ubSourceRegulars == 0 ) ) // Target full of militia, and source group only has greens + { + // Source group has nothing to offer the target group. + return 0; + } + + // If source sector is threatened by enemies, while target sector is not, do not move away! + else if (usSourceEnemiesInFiveSectors && !usTargetEnemiesInFiveSectors) + { + return 0; + } + } + + // Target sector has room for more militia? + if( ubTargetMilitia < ubMaxSquadSize ) + // Bonus rating iRes += DIR_WITH_UNFULL_SQUAD_RATING_BONUS; - if( NumEnemiesInSector( sDMapX, sDMapY ) ) + // Modify desire for militia-initiated attack + if( ubTargetEnemies ) { - // if( GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR ) - iDiff = (INT32)( (FLOAT)iRes * ( (FLOAT)CountAllMilitiaInFiveSectors( sDMapX, sDMapY ) / (FLOAT)NumEnemiesInFiveSectors( sDMapX, sDMapY ) ) ); - // else - // iDiff = iRes * ( (FLOAT)CountAllMilitiaInFiveSectors( sMapX, sMapY ) / DIV_OF_ORIGINAL_MILITIA / (FLOAT)NumEnemiesInFiveSectors( sDMapX, sDMapY ) ); + iDiff = (FLOAT)(usTargetMilitiaInFiveSectors / usTargetEnemiesInFiveSectors); - if( iDiff > (INT32)( (FLOAT)DIR_MIN_DIF * (FLOAT)iRes ) ) - iRes = iDiff; + if( iDiff > (FLOAT)DIR_MIN_DIF ) + iRes = (INT32)((FLOAT)iRes * iDiff); else iRes = 0; } -// There's player's mercs! Go there - if( PlayerMercsInSector_MSE( (UINT8)sDMapX, (UINT8)sDMapY, FALSE ) ) - iRes += 15000;// should be enough + // Test for player mercs in target sector. + if( PlayerMercsInSector_MSE( (UINT8)sDMapX, (UINT8)sDMapY, FALSE )) + { + // There are mercs at the target. Increase movement desire. + if ( bTargetTownId == BLANK_SECTOR && !fTargetSAMSite) + { + iRes += 15000;// should be enough + } + // If the target is a city, only 50% chance to increase the desire. This avoids situations where + // a Mobile group is stuck next to a merc-occupied city sector, unable to get in and unable to + // move away... + else if (((bTargetTownId != BLANK_SECTOR && gfMilitiaAllowedInTown[bTargetTownId]) || fTargetSAMSite) && + Random(2)==0) + { + iRes += 15000; + } + } // ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"M %ld, E%ld,%ld %ld, Diff %ld", CountAllMilitiaInSector( sMapX, sMapY ), sDMapX, sDMapY, NumEnemiesInSector( sDMapX, sDMapY ), ((INT32)CountAllMilitiaInSector( sMapX, sMapY )) - ((INT32)NumEnemiesInSector( sDMapX, sDMapY )) ); @@ -527,49 +930,306 @@ UINT16 CountDirectionRating( INT16 sMapX, INT16 sMapY, UINT8 uiDir ) // Standard conditions the direction should fit -BOOLEAN CheckStandardConditionsForDirection( INT16 sSMapX, INT16 sSMapY, INT16 sMapX, INT16 sMapY, BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ) +///////////////////////////////////////////////// +// HEADROCK HAM 3.4: Please note that the following function is used for ANY restrictions on movement. It tests +// everything from "groundbarriers" (impassable sector edge), to Player-Set Mobile Militia Restrictions, to hardcoded +// stuff like whether militia can move in or out of cities (or any other sector, for that matter). +// +// If you wish to change where militia can and can't move to, THIS IS IT. +// All sub-functions called here are related to the same purpose. +// +BOOLEAN CheckStandardConditionsForDirection( INT16 sSMapX, INT16 sSMapY, INT16 sMapX, INT16 sMapY, BOOLEAN fForTraining, BOOLEAN fForBattle ) { - UINT8 uiTravab = GetTraversability( SECTOR( sSMapX , sSMapY ) , SECTOR( sMapX, sMapY ) ); - BOOLEAN fTargetCityAllowsRoaming; + SECTORINFO * pTargetSector = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); - if( uiTravab == GROUNDBARRIER || uiTravab == EDGEOFWORLD ) return FALSE; - - if( !fForBattle && gfMSBattle && NumEnemiesInSector( sMapX, sMapY ) ) return FALSE; - - // 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) + if (fForBattle) { - fTargetCityAllowsRoaming = TRUE; + // When checking for reinforcements for battle, everything is THE OTHER WAY AROUND. Don't ask me why. + INT16 sTempMap; + sTempMap = sMapX; + sMapX = sSMapX; + sSMapX = sTempMap; + + sTempMap = sMapY; + sMapY = sSMapY; + sSMapY = sTempMap; + } + + // At the end of the function we will check the status of these flags to determine + // whether movement is indeed allowed. + BOOLEAN fTraverseAllowed = FALSE; + BOOLEAN fReadyForBattle = FALSE; + BOOLEAN fRoamingAllowed = FALSE; + BOOLEAN fCanBecomeGarrison = FALSE; + BOOLEAN fCanSimpleMove = FALSE; + + + ////////////////////////////////////////////////////////////////////////////////////////// + // The first check makes sure that travel between the sectors is allowed at all. Ground-Barriers + // are special because they also prevent merc and enemy movement. + UINT8 uiTravab = GetTraversability( SECTOR( sSMapX , sSMapY ) , SECTOR( sMapX, sMapY ) ); + + if( uiTravab == GROUNDBARRIER || uiTravab == EDGEOFWORLD ) + { + fTraverseAllowed = FALSE; } else { - if (gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities) + fTraverseAllowed = TRUE; + } + + ////////////////////////////////////////////////////////////////////////////////////////// + // If militia aren't set-up for battle, they can't move into an enemy sector. This prevents combat ever + // being instigated by militia unless specifically requested to do so by setting all the appropriate "ready!" + // flags earlier in the code. + if( !fForBattle && gfMSBattle && NumEnemiesInSector( sMapX, sMapY ) ) + { + fReadyForBattle = FALSE; + } + else + { + fReadyForBattle = TRUE; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // Check player-set restrictions. These are read from XML, and can be (but not always) influenced by + // which cities we've already liberated, and which sectors we've already explored. This is handled + // by a separate function. + if( gGameExternalOptions.gflimitedRoaming && !IsSectorRoamingAllowed( SECTOR(sMapX, sMapY) ) ) + { + // HEADROCK HAM 3.6: Training mobiles will also not take restrictions into account. + if ( (fForBattle && fReadyForBattle) || + fForTraining ) { - if ( SectorInfo[SECTOR( sMapX, sMapY )].fSurfaceWasEverPlayerControlled ) - { - fTargetCityAllowsRoaming = ( !gfMilitiaAllowedInTown[GetTownIdForSector( sMapX, sMapY )] ); - } - else - { - fTargetCityAllowsRoaming = FALSE; - } + // Allow movement into restricted sectors ONLY if they are for battle! + fRoamingAllowed = TRUE; } else - { - fTargetCityAllowsRoaming = FALSE; + { + fRoamingAllowed = FALSE; + } + } + else + { + fRoamingAllowed = TRUE; + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////// + // Movement into and out of city/sam sectors is more complicated to check. + // + // Movement out of a town/sam sector is only allowed when reinforcing other town/sam sectors. This means militia + // don't move around town unless called to do so. + // Movement from the wilderness into a town/sam is different in HAM 3.3, where roaming militias can go into town + // and turn into Garrisons (regular standing militia). They can also reinforce the city when it comes under attack, + // but only if allowed to do so by the current GameSettings. + // Please note that Minor Cities are considered halfway between the two. These are cities like Estoni or + // San Mona, where Militia cannot be trained. Roaming Militia can now be allowed to travel through these as they + // would through wilderness areas. It also includes larger facilities like Tixa and Orta. Militia will not stay + // there as garrisons, but may simply move through. + + //////////////////////////////////////////////////////////////////////////////////////////////// + // Begin by setting flags for the source sector + + BOOLEAN fSourceIsMajorTown = FALSE; + BOOLEAN fSourceIsMinorTown = FALSE; + BOOLEAN fSourceIsSamSite = FALSE; + + if (GetTownIdForSector( sSMapX, sSMapY) != BLANK_SECTOR) + { + // A "Major" city is one where militia can be trained. Drassen, Chitzena, Meduna, etcetera. + if (gfMilitiaAllowedInTown[GetTownIdForSector( sSMapX, sSMapY )]) + { + fSourceIsMajorTown = TRUE; + fSourceIsMinorTown = FALSE; + } + // A "Minor" city cannot train militia. This includes places like San Mona, Omerta, + // and even Tixa and Estoni + else + { + fSourceIsMajorTown = FALSE; + fSourceIsMinorTown = TRUE; + } + } + else + { + fSourceIsSamSite = IsThisSectorASAMSector( sSMapX, sSMapY, 0 ); + } + + // And the target sector + + BOOLEAN fTargetIsMajorTown = FALSE; + BOOLEAN fTargetIsMinorTown = FALSE; + BOOLEAN fTargetIsSamSite = FALSE; + + if (GetTownIdForSector( sMapX, sMapY) != BLANK_SECTOR) + { + if (gfMilitiaAllowedInTown[GetTownIdForSector( sMapX, sMapY )]) + { + fTargetIsMajorTown = TRUE; + fTargetIsMinorTown = FALSE; + } + else + { + fTargetIsMajorTown = FALSE; + fTargetIsMinorTown = TRUE; + } + } + else + { + fTargetIsSamSite = IsThisSectorASAMSector( sMapX, sMapY, 0 ); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + + // First we run the test for battle reinforcement conditions. + if ( fForBattle && fReadyForBattle ) + { + // Is reinforcement allowed only in cities? + if (gGameExternalOptions.gfAllowReinforcementsOnlyInCity) + { + // Make sure both origin and destination are cities. + if ( fSourceIsMajorTown && fTargetIsMajorTown ) + { + fReadyForBattle = TRUE; + } + else + { + // Reinforcing from Wilderness is impossible. + fReadyForBattle = FALSE; + } + } + + // Battle reinforcements allowed from both cities and wilderness sectors. + else + { + // Trying to do battle reinforcement from a town/sam to the wilderness? + if ( ( fSourceIsMajorTown || fSourceIsSamSite ) && + !(fTargetIsMajorTown || fTargetIsSamSite ) ) + { + // No! Static troops cannot reinforce Mobile troops for any reason. + fReadyForBattle = FALSE; + } + else + { + fReadyForBattle = TRUE; + } + } + + // RETURN. + if ( fTraverseAllowed && fReadyForBattle && fRoamingAllowed ) + { + return TRUE; + } + else + { + return 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 ) ) + // Else, if we are doing a "peaceful" movement. + else if ( !fForBattle ) + { + + // Are we trying to bolster a local garrison? + if (fTargetIsSamSite || fTargetIsMajorTown) + { + // Flags to store INI option settings. + + BOOLEAN fAllowReinforceCities = ( gGameExternalOptions.fAllowMobileReinforceCities && gGameExternalOptions.gfmusttrainroaming && gGameExternalOptions.gfAllowMilitiaGroups ); + BOOLEAN fAllowReinforceSAM = ( gGameExternalOptions.fAllowMobileReinforceSAM && gGameExternalOptions.gfmusttrainroaming && gGameExternalOptions.gfAllowMilitiaGroups ); + + // Town->Town, Town->SAM, SAM->Town and SAM->SAM are disallowed for peaceful movement. + if ( fSourceIsMajorTown || fSourceIsSamSite ) + { + fCanBecomeGarrison = FALSE; + } + // Wilderness->Sam/Town. + else + { + if (!NumEnemiesInSector( sMapX, sMapY ) && // Is the sector under our control at the moment? + pTargetSector->fSurfaceWasEverPlayerControlled && // Has it ever been under control? + CountMilitia(pTargetSector) < gGameExternalOptions.iMaxMilitiaPerSector ) // Is there room here for more militia? + { + if (fTargetIsSamSite && fAllowReinforceSAM) + { + fCanBecomeGarrison = TRUE; + } + else if (fTargetIsMajorTown && fAllowReinforceCities) + { + fCanBecomeGarrison = TRUE; + } + } + else + { + fCanBecomeGarrison = FALSE; + } + } + + // RETURN. + if ( fTraverseAllowed && fRoamingAllowed && fCanBecomeGarrison ) + { + return TRUE; + } + else + { + return FALSE; + } + } + + // Trying to move peacefully out of a Town/SAM? + else if (fSourceIsMajorTown || fSourceIsSamSite) + { + // This should ONLY be possible if we are explicitly training new militia groups. + // Also, militia can no longer spawn directly into enemy-controlled territory. + if (fForTraining && !(NumEnemiesInSector( sMapX, sMapY ))) + { + fCanSimpleMove = TRUE; + } + else + { + // Otherwise always impossible. We don't want militia leaving the cities for any other reason! + fCanSimpleMove = FALSE; + } + } + + // Test for Wilderness/Minor -> Minor + else if (fTargetIsMinorTown) + { + // Is allowed by INI? + if (gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities && + pTargetSector->fSurfaceWasEverPlayerControlled ) // Have we visited this sector before? + { + // Fine then. Works the same as wilderness->wilderness. + fCanSimpleMove = TRUE; + } + else + { + // Movement through Minor Cities isn't allowed. + fCanSimpleMove = FALSE; + } + } + // Wilderness/Minor -> Wilderness + else + { + // Always possible. + fCanSimpleMove = TRUE; + } + + // RETURN. + if ( fTraverseAllowed && fRoamingAllowed && fCanSimpleMove ) + { + return TRUE; + } + else + { + return FALSE; + } + } + + return FALSE; } -void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ) +void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fForTraining, BOOLEAN fForBattle ) { *uiDirNumber = 0; @@ -577,7 +1237,7 @@ void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT1 if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld-------------------", sMapX, sMapY ); #endif - if(sMapY > MINIMUM_VALID_Y_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY - 1, fWithCities, fForBattle, fOnlyCitySectors ) ) + if(sMapY > MINIMUM_VALID_Y_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY - 1, fForTraining, fForBattle ) ) { pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY - 1 ); @@ -594,7 +1254,7 @@ void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT1 ++(*uiDirNumber); } - if(sMapX > MINIMUM_VALID_X_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX - 1, sMapY, fWithCities, fForBattle, fOnlyCitySectors )) + if(sMapX > MINIMUM_VALID_X_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX - 1, sMapY, fForTraining, fForBattle )) { pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX - 1, sMapY ); @@ -611,7 +1271,7 @@ void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT1 ++(*uiDirNumber); } - if(sMapY < MAXIMUM_VALID_Y_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY + 1, fWithCities, fForBattle, fOnlyCitySectors )) + if(sMapY < MAXIMUM_VALID_Y_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY + 1, fForTraining, fForBattle )) { pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY + 1 ); @@ -628,7 +1288,7 @@ void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT1 ++(*uiDirNumber); } - if(sMapX < MAXIMUM_VALID_X_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX + 1, sMapY, fWithCities, fForBattle, fOnlyCitySectors )) + if(sMapX < MAXIMUM_VALID_X_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX + 1, sMapY, fForTraining, fForBattle )) { pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX + 1, sMapY ); @@ -732,15 +1392,18 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ) if( GetWorldHour() % 2 )return; memset(pMoveDir, 0, sizeof(pMoveDir)); - GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, FALSE, FALSE ); + GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, FALSE ); if( uiDirNumber ) { for( x = 1; x < uiDirNumber ; ++x )pMoveDir[x][1] += pMoveDir[x-1][1]; // ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld", x, pMoveDir[x][1]); - iRandom = Random( pMoveDir[ uiDirNumber - 1 ][1] + - ( uiMilitiaCount >= gGameExternalOptions.guiMinMilitiaSquadSize ? CHANCE_TO_MOVE_A_SQUAD : CHANCE_TO_MOVE_AN_UNFULL_SQUAD ) ); + // HEADROCK HAM 3.6: Too many INI settings. Removed a couple, including MIN/MAX SQUAD SIZE. +// iRandom = Random( pMoveDir[ uiDirNumber - 1 ][1] + +// ( uiMilitiaCount >= gGameExternalOptions.guiMinMilitiaSquadSize ? CHANCE_TO_MOVE_A_SQUAD : CHANCE_TO_MOVE_AN_UNFULL_SQUAD ) ); + iRandom = Random( pMoveDir[ uiDirNumber - 1 ][1] ); + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Roll %ld", iRandomRes); @@ -769,65 +1432,11 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ) // define which sectors are restricted, based on city conquest. /////////////////////////////////////////////////////////////////// + // HEADROCK HAM 3.4: Moved the check to a separate function. if (gGameExternalOptions.gflimitedRoaming) { - // Is militia allowed to move through visited sectors? - if (gGameExternalOptions.bUnrestrictVisited) - { - // Scan each sector restricted in the XML. - for (INT32 i=0;i= 0 && targetX < MAP_WORLD_X); Assert(targetY >= 0 && targetY < MAP_WORLD_Y); MoveMilitiaSquad( sMapX, sMapY, targetX, targetY, FALSE ); - AddToBlockMoveList( targetX, targetY ); if ( gfStrategicMilitiaChangesMade) { @@ -879,30 +1487,34 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ) // called if there is a delay set. void CreateMilitiaSquads(INT16 sMapX, INT16 sMapY ) { + // Variables for Direction Rating checks UINT16 pMoveDir[4][3]; UINT8 uiDirNumber = 0; UINT32 iRandomRes = 0; UINT8 x;//,y; - SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); - UINT8 uiMilitiaCount; - // HEADROCK HAM B2.7: New values: + // Variables for checking other sectors in the same city UINT16 iCounter; - BOOLEAN fFoundValidSector; UINT16 bTownId; + // Tracks X/Y coordinates of another possible sector to train from (within same city) INT16 sCurrentX, sCurrentY; + // Smart Militia Generator now locates the best sector for placement. + UINT16 usBestLocalSectorID = 0; + UINT16 usBestPerimeterSectorID = 0; + UINT16 usBestLocalSectorRating = 0; + UINT16 usBestPerimeterSectorRating = 0; + // Selected Target coordinates + UINT8 sTMapX = 0; + UINT8 sTMapY = 0; - // If we're not allowing roaming groups, - // then we're not creating them either. - // Remove this if you want to create them - // for some other reason. But you also have to - // remove the same line in UpdateMilitiaSquads - // Or the ones you create won't move. - // This function is only called from UpdateMilitiaSquads - // and TownMilitiaTrainingCompleted + // Leadership may affect quantity of militia generated. + UINT8 ubBestLeadership = FindBestMilitiaTrainingLeadershipInSector ( sMapX, sMapY, 0, MOBILE_MILITIA ); + + // If we're not allowing roaming groups, then we're not creating them either. if( !gGameExternalOptions.gfAllowMilitiaGroups ) return; + // Reset block move list. Does this cause issues with training in A1? Probably not. if( sMapX == 1 && sMapY == 1 ) { ClearBlockMoveList(); @@ -911,138 +1523,115 @@ void CreateMilitiaSquads(INT16 sMapX, INT16 sMapY ) if( CheckInBlockMoveList( sMapX, sMapY ) )return; - uiMilitiaCount = CountMilitia(pSectorInfo); + // Check all sectors adjacent to the training sector itself. + GenerateDirectionInfosForTraining( sMapX, sMapY, &uiDirNumber, pMoveDir ); - if( !uiMilitiaCount )return; - - // Create new Militia Squad + if(uiDirNumber)// At least one available sector found, adjacent to Source Training Sector + { + for ( x = 0; x < uiDirNumber; x++) + { + // Best rating so far? + if (pMoveDir[x][1] > usBestLocalSectorRating) + { + // Mark this as the best "local" destination. + usBestLocalSectorID = pMoveDir[x][0]; + usBestLocalSectorRating = pMoveDir[x][1]; + } + } + } + // If we're training inside a city, check other city sectors for nearby available space. Maybe you'll find + // a better place to put new mobiles? bTownId = GetTownIdForSector( sMapX, sMapY ); - if( bTownId != BLANK_SECTOR ) { - fFoundValidSector = FALSE; + // We're training in a city. + iCounter = 0; - // Check all sectors adjacent to the training sector itself. - GenerateDirectionInfosWithCapacityCheck( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, FALSE, FALSE ); - - if(uiDirNumber)// && Random(100) < CHANCE_TO_GENERATE_A_SQUAD) + // Go through each city in the game + while( pTownNamesList[ iCounter ] != 0 ) { - for( x = 1; x < uiDirNumber ; ++x )pMoveDir[x][1] += pMoveDir[x-1][1]; + // Are we in this city? + if( pTownNamesList[ iCounter] == bTownId ) + { + sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + sCurrentY = GET_Y_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); - iRandomRes = Random( pMoveDir[ uiDirNumber - 1 ][1] ); - - for( x = 0; x < uiDirNumber; ++x) - if( iRandomRes < pMoveDir[x][1] ) + // if sector has enemies or hasn't already been taken at least once, then + if ( !SectorInfo[ SECTOR(sCurrentX, sCurrentY) ].fSurfaceWasEverPlayerControlled || + NumEnemiesInSector( sCurrentX, sCurrentY ) > 0 ) { - iRandomRes = x; - break; + // skip the rest. Generate no militia from this sector. + iCounter++; + continue; } - // shouldn't be! - if(iRandomRes >= uiDirNumber)iRandomRes = 0; + // Find out if any adjacent sectors have room in them. + GenerateDirectionInfosForTraining( sCurrentX, sCurrentY, &uiDirNumber, pMoveDir ); - //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) + if(uiDirNumber)// At least one available adjacent sector has been found { - //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] ) ); -// } - } - - } - // 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 ) + // Go through each adjacent sector + for( x = 0; x < uiDirNumber ; x++ ) { - // 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] ) ) ) + // Have we found the best rating so far? + if ( pMoveDir[x][1] > usBestPerimeterSectorRating ) { - /* 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] ) ); - // } + usBestPerimeterSectorID = pMoveDir[x][0]; + usBestPerimeterSectorRating = pMoveDir[x][1]; } } - } - iCounter++; + } + iCounter++; + } + } + + // Randomly choose whether to place locally or somewhere else around the city perimeter. + // Local and Perimeter have separate "weights" in this randomization, but you're much more + // likely to place militia near the original source sector. That is because it has increased + // chance of being selected both as the best local and as the best overall, and so might be + // selected as the result of either random choice. + + // Make sure at least one sector was found with more than 0 rating + if (usBestLocalSectorRating > 0 || usBestPerimeterSectorRating > 0) + { + // Found at least one location. + iRandomRes = PreRandom( usBestLocalSectorRating + usBestPerimeterSectorRating ); + // Either go with randomization, or automatically select a Local sector if there is no better Perimeter sector. + if (iRandomRes < usBestLocalSectorRating || + usBestLocalSectorRating >= usBestPerimeterSectorRating) + { + sTMapX = SECTORX( usBestLocalSectorID ); + sTMapY = SECTORY( usBestLocalSectorID ); + + // Generate militia in the best sector immediately adjacent to the Source Training Sector. + GenerateMilitiaSquad( sMapX, sMapY, sTMapX, sTMapY, ubBestLeadership ); + AddToBlockMoveList( sTMapX, sTMapY ); + + // Check for enemies in target sector + if( NumEnemiesInSector( sTMapX, sTMapY ) ) + { + // Initiave battle + gfMSBattle = TRUE; + EnterAutoResolveMode( sTMapX, sTMapY ); + } + } + else + { + sTMapX = SECTORX( usBestPerimeterSectorID ); + sTMapY = SECTORY( usBestPerimeterSectorID ); + + // Generate militia in the best sector found in this city's perimeter. + GenerateMilitiaSquad( sCurrentX, sCurrentY, sTMapX, sTMapY, ubBestLeadership ); + AddToBlockMoveList( sTMapX, sTMapY ); + + // Check for enemies in target sector + if( NumEnemiesInSector( sTMapX, sTMapY ) ) + { + // Initiave battle + gfMSBattle = TRUE; + EnterAutoResolveMode( sTMapX, sTMapY ); } } } @@ -1065,8 +1654,7 @@ void DoMilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY ) guiDirNumber = 0; - GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, FALSE ); + GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, TRUE ); ZeroMemory( gpAttackDirs, sizeof( gpAttackDirs ) ); @@ -1078,7 +1666,7 @@ void DoMilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY ) guiDirNumber = uiDirNumber + 1; x = 0; - while( CountMilitia(pSectorInfo ) < gGameExternalOptions.guiMaxMilitiaSquadSizeBattle && + while( CountMilitia(pSectorInfo ) < gGameExternalOptions.iMaxMilitiaPerSector && ( fMoreTroopsLeft[0] || fMoreTroopsLeft[1] || fMoreTroopsLeft[2] || fMoreTroopsLeft[3] || fFirstLoop ) ) { fMoreTroopsLeft[ x ] = MoveOneBestMilitiaMan( SECTORX( pMoveDir[ x ][0] ), SECTORY( pMoveDir[ x ][0] ), sMapX, sMapY ); @@ -1134,8 +1722,7 @@ BOOLEAN IsThereMilitiaInAdjacentSector( INT16 sMapX, INT16 sMapY ) UINT8 x; BOOLEAN fResult = FALSE; - GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, FALSE ); + GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, TRUE ); for( x = 0; x < uiDirNumber ; ++x ) if( CountAllMilitiaInSector( SECTORX( pMoveDir[ x ][ 0 ] ), SECTORY( pMoveDir[ x ][ 0 ] ) ) ) @@ -1148,6 +1735,10 @@ void MilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY ) { sMSMapX = sMapX; sMSMapY = sMapY; + CHAR16 pStr[256]; + CHAR16 pStr2[128]; + + GetSectorIDString( sMapX, sMapY, 0, pStr2, FALSE ); if( !gGameExternalOptions.gfAllowReinforcements ) return; @@ -1161,7 +1752,12 @@ void MilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY ) // This is no longer a question of simply whether to have a full militia count, but also whether we want // reinforcements. So if there are any available, always ask. if( IsThereMilitiaInAdjacentSector( sMapX, sMapY ) ) // && CountAllMilitiaInSector( sMapX, sMapY ) < gGameExternalOptions.guiMaxMilitiaSquadSizeBattle ) - DoScreenIndependantMessageBox( gzCWStrings[0], MSG_BOX_FLAG_YESNO, MSCallBack ); + { + // HEADROCK HAM 3.5: Added sector name to reinforcements string. + swprintf(pStr, gzCWStrings[0], pStr2); + //DoScreenIndependantMessageBox( gzCWStrings[0], MSG_BOX_FLAG_YESNO, MSCallBack ); + DoScreenIndependantMessageBox( pStr, MSG_BOX_FLAG_YESNO, MSCallBack ); + } } void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ) @@ -1180,7 +1776,9 @@ void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ) // 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() + +// HEADROCK HAM 3.4: This hardcoded function has been made obsolete by XML externalization. +/*void AdjustRoamingRestrictions() { if (!gGameExternalOptions.bDynamicRestrictRoaming) return; @@ -1213,6 +1811,18 @@ void AdjustRoamingRestrictions() // ROAD SOUTH TO ALMA gDynamicRestrictMilitia[SECTOR(12,6)] = true; + + // HEADROCK HAM 3.3: Drassen SAM-Site (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(15,4)] = true; + + // HEADROCK HAM 3.3: Drassen ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(13,2)] = true; + gDynamicRestrictMilitia[SECTOR(13,3)] = true; + gDynamicRestrictMilitia[SECTOR(13,4)] = true; + + // HEADROCK HAM 3.3: Forgot Omerta! + gDynamicRestrictMilitia[SECTOR(9,1)] = true; + gDynamicRestrictMilitia[SECTOR(10,1)] = true; } @@ -1234,6 +1844,12 @@ void AdjustRoamingRestrictions() // ROAD SOUTH gDynamicRestrictMilitia[SECTOR(14,11)] = true; + + // HEADROCK HAM 3.3: Alma ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(13,8)] = true; + gDynamicRestrictMilitia[SECTOR(14,8)] = true; + gDynamicRestrictMilitia[SECTOR(13,9)] = true; + gDynamicRestrictMilitia[SECTOR(14,9)] = true; } if ( IsTownUnderCompleteControlByPlayer(CHITZENA) ) @@ -1250,6 +1866,13 @@ void AdjustRoamingRestrictions() // SAM SITE DEFENSE / ROAD SOUTH gDynamicRestrictMilitia[SECTOR(3,4)] = true; gDynamicRestrictMilitia[SECTOR(3,5)] = true; + + // HEADROCK HAM 3.3: Chitzena SAM-Site (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(2,4)] = true; + + // HEADROCK HAM 3.3: Chitzena ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(2,1)] = true; + gDynamicRestrictMilitia[SECTOR(2,2)] = true; } if ( IsTownUnderCompleteControlByPlayer(CAMBRIA) ) @@ -1281,6 +1904,20 @@ void AdjustRoamingRestrictions() // ROAD TO ALMA gDynamicRestrictMilitia[SECTOR(11,7)] = true; + + // HEADROCK HAM 3.3: Cambria SAM-Site (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(8,9)] = true; + + // HEADROCK HAM 3.3: Cambria ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(8,6)] = true; + gDynamicRestrictMilitia[SECTOR(9,6)] = true; + gDynamicRestrictMilitia[SECTOR(8,7)] = true; + gDynamicRestrictMilitia[SECTOR(9,7)] = true; + gDynamicRestrictMilitia[SECTOR(8,8)] = true; + + // HEADROCK HAM 3.3: Forgot Omerta! + gDynamicRestrictMilitia[SECTOR(9,1)] = true; + gDynamicRestrictMilitia[SECTOR(10,1)] = true; } if ( IsTownUnderCompleteControlByPlayer(BALIME) ) @@ -1305,6 +1942,10 @@ void AdjustRoamingRestrictions() gDynamicRestrictMilitia[SECTOR(8,14)] = true; gDynamicRestrictMilitia[SECTOR(9,14)] = true; gDynamicRestrictMilitia[SECTOR(10,14)] = true; + + // HEADROCK HAM 3.3: Balime ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(11,12)] = true; + gDynamicRestrictMilitia[SECTOR(12,12)] = true; } if ( IsTownUnderCompleteControlByPlayer(GRUMM) ) @@ -1331,6 +1972,24 @@ void AdjustRoamingRestrictions() gDynamicRestrictMilitia[SECTOR(2,10)] = true; gDynamicRestrictMilitia[SECTOR(3,10)] = true; gDynamicRestrictMilitia[SECTOR(2,11)] = true; + + // HEADROCK HAM 3.3: Grumm ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(1,7)] = true; + gDynamicRestrictMilitia[SECTOR(2,7)] = true; + gDynamicRestrictMilitia[SECTOR(1,8)] = true; + gDynamicRestrictMilitia[SECTOR(2,8)] = true; + gDynamicRestrictMilitia[SECTOR(3,8)] = true; + } + + if ( IsTownUnderCompleteControlByPlayer(MEDUNA) ) + { + // HEADROCK HAM 3.3: Meduna ITSELF (for reinforcement purposes) + gDynamicRestrictMilitia[SECTOR(3,14)] = true; + gDynamicRestrictMilitia[SECTOR(4,14)] = true; + gDynamicRestrictMilitia[SECTOR(5,14)] = true; + gDynamicRestrictMilitia[SECTOR(3,15)] = true; + gDynamicRestrictMilitia[SECTOR(4,15)] = true; + gDynamicRestrictMilitia[SECTOR(3,16)] = true; } if ( IsTownUnderCompleteControlByPlayer(DRASSEN) && IsTownUnderCompleteControlByPlayer(CHITZENA) ) @@ -1437,98 +2096,259 @@ void AdjustRoamingRestrictions() gDynamicRestrictMilitia[SECTOR(6,13)] = true; gDynamicRestrictMilitia[SECTOR(6,14)] = true; } +}*/ + +void AdjustRoamingRestrictions() +{ + UINT32 uiCapturedTownsFlag = 0; + UINT16 cnt = 0; + + if (!gGameExternalOptions.fDynamicRestrictRoaming) + return; + + // to do: Add something to clean up the entire array before setting the flags... + + for (cnt=1; cnt < NUM_TOWNS; cnt++) + { + if ( IsTownUnderCompleteControlByPlayer((UINT8)cnt) ) + { + // Flag town as captured. + uiCapturedTownsFlag |= (1 << (cnt-1)); + } + } + + for (cnt=0; cnt < 256; cnt++) + { + gDynamicRestrictMilitia[cnt] = false; + } + + for (cnt=0; (cnt < 5001 && gDynamicRestrictions[cnt].bSectorID >= 0); cnt++) + { + if (!(~uiCapturedTownsFlag & gDynamicRestrictions[cnt].uiReqTownFlags)) + { + gDynamicRestrictMilitia[gDynamicRestrictions[cnt].bSectorID] = 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. +// when generating militia. This function checks for available space at the target location. The stored results +// indicate the amount of space left in the target sector. +// HEADROCK HAM 3.6: Revamped to fix bugs. -void GenerateDirectionInfosWithCapacityCheck( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ) +void GenerateDirectionInfosForTraining( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3] ) { SECTORINFO *pSectorInfo; + UINT8 ubMaxMilitia = gGameExternalOptions.iMaxMilitiaPerSector; *uiDirNumber = 0; + UINT16 usMilitiaCapacity = 0; + UINT16 usUpgradeCapacity = 0; + + // Clean up pMoveDir + for (UINT8 x = 0; x < 4; x++) + { + pMoveDir[x][0]=0; + pMoveDir[x][1]=0; + pMoveDir[x][2]=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(sMapY > MINIMUM_VALID_Y_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY - 1, fWithCities, fForBattle, fOnlyCitySectors ) && - pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] < gGameExternalOptions.iMaxMilitiaPerSector ) + // Check north + if( sMapY > MINIMUM_VALID_Y_COORDINATE ) { - pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY - 1 ); + pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY - 1 ) ] ); - if( fForBattle ) - pMoveDir[ *uiDirNumber ][1] = 0; - else - pMoveDir[ *uiDirNumber ][1] = CountDirectionRating( sMapX, sMapY, DIR_NORTH ); + // Are militia allowed to travel in that direction from the current sector, for training purposes? + if ( CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY - 1, TRUE, FALSE ) ) + { + // Count free slots. Each slot is worth 3 points. + usMilitiaCapacity = 3 * (ubMaxMilitia - CountMilitia( pSectorInfo )); + // Count militia that can be upgraded. Regular = 1 point, Green = 2 points. + usUpgradeCapacity = MilitiaUpgradeSlotsCheck( pSectorInfo ); - 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(sMapX > MINIMUM_VALID_X_COORDINATE && 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(sMapY < MAXIMUM_VALID_Y_COORDINATE && 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(sMapX < MAXIMUM_VALID_X_COORDINATE && 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); + // Found at least one slot here? + if (usUpgradeCapacity || usMilitiaCapacity) + { + // Store data in return variable. + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY - 1 ); + pMoveDir[ *uiDirNumber ][1] = usMilitiaCapacity + usUpgradeCapacity; + 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); + } + } } + // Check west + if( sMapX > MINIMUM_VALID_X_COORDINATE ) + { + pSectorInfo = &( SectorInfo[ SECTOR( sMapX - 1, sMapY ) ] ); + + // Are militia allowed to travel in that direction from the current sector, for training purposes? + if ( CheckStandardConditionsForDirection( sMapX, sMapY, sMapX - 1, sMapY, TRUE, FALSE ) ) + { + // Count free slots. Each slot is worth 3 points. + usMilitiaCapacity = 3 * (ubMaxMilitia - CountMilitia( pSectorInfo )); + // Count militia that can be upgraded. Regular = 1 point, Green = 2 points. + usUpgradeCapacity = MilitiaUpgradeSlotsCheck( pSectorInfo ); + + // Found at least one slot here? + if (usUpgradeCapacity || usMilitiaCapacity) + { + // Store data in return variable. + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX - 1, sMapY ); + pMoveDir[ *uiDirNumber ][1] = usMilitiaCapacity + usUpgradeCapacity; + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_WEST; + + #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); + } + } + } + + // Check south + if( sMapY < MAXIMUM_VALID_Y_COORDINATE ) + { + pSectorInfo = &( SectorInfo[ SECTOR( sMapX , sMapY + 1 ) ] ); + + // Are militia allowed to travel in that direction from the current sector, for training purposes? + if ( CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY + 1, TRUE, FALSE ) ) + { + // Count free slots. Each slot is worth 3 points. + usMilitiaCapacity = 3 * (ubMaxMilitia - CountMilitia( pSectorInfo )); + // Count militia that can be upgraded. Regular = 1 point, Green = 2 points. + usUpgradeCapacity = MilitiaUpgradeSlotsCheck( pSectorInfo ); + + // Found at least one slot here? + if (usUpgradeCapacity || usMilitiaCapacity) + { + // Store data in return variable. + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY + 1 ); + pMoveDir[ *uiDirNumber ][1] = usMilitiaCapacity + usUpgradeCapacity; + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_SOUTH; + + #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); + } + } + } + + // Check east + if( sMapX < MAXIMUM_VALID_X_COORDINATE ) + { + pSectorInfo = &( SectorInfo[ SECTOR( sMapX + 1 , sMapY ) ] ); + + // Are militia allowed to travel in that direction from the current sector, for training purposes? + if ( CheckStandardConditionsForDirection( sMapX, sMapY, sMapX +1 , sMapY, TRUE, FALSE ) ) + { + // Count free slots. Each slot is worth 3 points. + usMilitiaCapacity = 3 * (ubMaxMilitia - CountMilitia( pSectorInfo )); + // Count militia that can be upgraded. Regular = 1 point, Green = 2 points. + usUpgradeCapacity = MilitiaUpgradeSlotsCheck( pSectorInfo ); + + // Found at least one slot here? + if (usUpgradeCapacity || usMilitiaCapacity) + { + // Store data in return variable. + pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX + 1, sMapY ); + pMoveDir[ *uiDirNumber ][1] = usMilitiaCapacity + usUpgradeCapacity; + pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_EAST; + + #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); + } + } + } } + +// HEADROCK HAM 3.4: Something that should've been done a long time ago - moving the check of roaming restrictions +// to a separate function. Returns TRUE if the sector can be traversed by militia. +BOOLEAN IsSectorRoamingAllowed( UINT32 uiSector ) +{ + + // EXPLORATION-BASED RESTRICTION + // Is militia allowed to move through visited sectors? + if (gGameExternalOptions.fUnrestrictVisited) + { + // Has the sector ever been visited? + if ( SectorInfo[ uiSector ].fSurfaceWasEverPlayerControlled ) + { + // Always return TRUE. + return TRUE; + } + } + // Please note that with exploration restrictions, an UNVISITED sector is not neccesarily RESTRICTED. + + // XML-BASED RESTRICTION + // Is destination allowed by the Dynamic Restrictions Array? + if( !gDynamicRestrictMilitia[ uiSector ] ) + { + // Don't restrict movement if there are player mercs in the target sector! Otherwise, the player + // cannot have militia follow him to attack the enemy. + if (!PlayerMercsInSector_MSE( SECTORX(uiSector), SECTORY(uiSector), FALSE )) + { + return FALSE; + } + } + + return TRUE; +} + +// HEADROCK HAM 3.6: Function returns the number of "upgrade slots" available at target sector. Basically, every GREEN +// Militia at the target sector counts as 2 upgrade slots, while every regular counts as 1. + +UINT16 MilitiaUpgradeSlotsCheck( SECTORINFO * pSectorInfo ) +{ + // Result variables + UINT16 usNumUpgradeSlots = 0; + + // Target sector full of elites? + if (pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] >= gGameExternalOptions.iMaxMilitiaPerSector) + { + // Militia cannot be placed or moved here at all. + return 0; + } + + // If not allowed to train elites ( or not yet ) + if (!gGameExternalOptions.gfTrainVeteranMilitia || // Not allowed at all + (GetWorldDay( ) < gGameExternalOptions.guiTrainVeteranMilitiaDelay)) // Not yet allowed. + { + // If sector full of Regulars and Elites? + if (pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]+pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] >= gGameExternalOptions.iMaxMilitiaPerSector) // Target sector full of regular+elite militia + { + // Can't upgrade Regulars to Elites, so the sector is already full. + return 0; + } + + // Each Green we can convert to Regular counts as 2 slots. + usNumUpgradeSlots = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA] * 2; + + return (usNumUpgradeSlots); + } + + // Allowed to train Green->Regular, and Regular->Elite + usNumUpgradeSlots = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA] * 2; + usNumUpgradeSlots += pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]; + + return (usNumUpgradeSlots); +} + diff --git a/Strategic/MilitiaSquads.h b/Strategic/MilitiaSquads.h index 7feedaba..7efcc042 100644 --- a/Strategic/MilitiaSquads.h +++ b/Strategic/MilitiaSquads.h @@ -5,6 +5,7 @@ #include "Types.h" #include "Soldier Control.h" +#include "campaign types.h" //#define MAXIMUM_MILITIA_SQUAD_SIZE 30 @@ -18,7 +19,8 @@ void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY ); void CreateMilitiaSquads(INT16 sMapX, INT16 sMapY ); void MilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY ); -void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fWithCities, BOOLEAN fForBattle, BOOLEAN fOnlyCitySectors ); +// HEADROCK HAM 3.4: Now using different arguments for this function. All relevent instances have also been altered. +void GenerateDirectionInfos( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3], BOOLEAN fForTraining, BOOLEAN fForBattle ); BOOLEAN MoveOneBestMilitiaMan(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY); void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ); @@ -29,9 +31,29 @@ 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 ); +BOOLEAN CheckStandardConditionsForDirection( INT16 sSMapX, INT16 sSMapY, INT16 sMapX, INT16 sMapY, BOOLEAN fForTraining, BOOLEAN fForBattle ); // 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 ); +void GenerateDirectionInfosForTraining( INT16 sMapX, INT16 sMapY, UINT8* uiDirNumber, UINT16 pMoveDir[4][3] ); + +// HEADROCK HAM 3.4: Stores restriction data from XML, including required cities for each sector to be allowed. +typedef struct DYNAMICRESTRICTIONS +{ + INT8 bSectorID; + UINT32 uiReqTownFlags; +} DYNAMICRESTRICTIONS; + +// HEADROCK HAM 3.4: New function for simple Roaming Restriction testing. +BOOLEAN IsSectorRoamingAllowed( UINT32 uiSector ); + +// HEADROCK HAM 3.6: New upgrade check returns the amount of militia that can be upgraded at target sector, in +// "upgrade points" +UINT16 MilitiaUpgradeSlotsCheck( SECTORINFO * pSectorInfo ); + +// HEADROCK HAM 3.6: This needs to be accessible. +void AddToBlockMoveList(INT16 sMapX, INT16 sMapY); +// And this: +UINT8 CountMilitia(SECTORINFO *pSectorInfo); + #endif \ No newline at end of file diff --git a/Strategic/Player Command.cpp b/Strategic/Player Command.cpp index f10526ca..d941c380 100644 --- a/Strategic/Player Command.cpp +++ b/Strategic/Player Command.cpp @@ -25,6 +25,8 @@ #include "Tactical Save.h" #include "interface Dialogue.h" #include "Random.h" + // HEADROCK HAM 3.6: Added for facility string printing... + #include "PopUpBox.h" #endif #include "postalservice.h" @@ -32,26 +34,70 @@ extern BOOLEAN fMapScreenBottomDirty; extern CPostalService gPostalService; -void GetSectorFacilitiesFlags( INT16 sMapX, INT16 sMapY, STR16 sFacilitiesString ) + +void AddFacilitiesToBox( INT16 sMapX, INT16 sMapY, UINT32 *uiHandle, BOOLEAN fCityInfoBox ) { // will build a string stating current facilities present in sector - if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags == 0 ) + // HEADROCK HAM 3.5: Facilities are now read from XMLs, including their names. + + UINT32 cnt = 0; + UINT32 uiNumFacilities = 0; + BOOLEAN fHeaderAdded = FALSE; + + CHAR16 szFacilityString[30]; + + Assert(sMapX > 0 && sMapY > 0 && sMapX < 17 && sMapY < 17); + + for ( cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++ ) { - // none - swprintf( sFacilitiesString, L"%s", sFacilitiesStrings[ 0 ] ); + if (fCityInfoBox && uiNumFacilities == 0 && !fHeaderAdded ) + { + // For a city info box, always show this line on the left side. + swprintf( szFacilityString, L"%s:", pwTownInfoStrings[ 8 ] ); + AddMonoString( uiHandle, szFacilityString ); + fHeaderAdded = TRUE; + } + // Facility type exists at this location? + if (gFacilityLocations[SECTOR(sMapX,sMapY)][cnt].fFacilityHere) + { + if (gFacilityLocations[SECTOR(sMapX,sMapY)][cnt].ubHidden == 0 || + (gFacilityLocations[SECTOR(sMapX,sMapY)][cnt].ubHidden == 1 && SectorInfo[SECTOR(sMapX,sMapY)].fSurfaceWasEverPlayerControlled) ) + { + if (!fCityInfoBox && uiNumFacilities == 0 && !fHeaderAdded ) + { + // For a non-city info box, show this line on the left side only if there's at least one facility. + swprintf( szFacilityString, L"%s:", pwTownInfoStrings[ 8 ] ); + AddMonoString( uiHandle, szFacilityString ); + } + else if (uiNumFacilities > 0) + { + // Add empty line on the left size to make room for text on the right. + AddMonoString( uiHandle, L"" ); + } + + AddSecondColumnMonoString( uiHandle, gFacilityTypes[cnt].szFacilityName ); + uiNumFacilities++; + } + } + } + + if( uiNumFacilities == 0 && fCityInfoBox ) + { + // Add "NONE" on the right side. Only happens when the sector is a city. + AddSecondColumnMonoString( uiHandle, sFacilitiesStrings[0] ); return; } - + /* HEADROCK HAM 3.4: Hardcodes removed. // hospital - if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & SFCF_HOSPITAL ) + if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & (1 << FACILITY_HOSPITAL) ) { - swprintf( sFacilitiesString, L"%s", sFacilitiesStrings[ 1 ] ); + swprintf( sFacilitiesString, L"%s", sFacilitiesStrings[ FACILITY_HOSPITAL ] ); } // industry - if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & SFCF_INDUSTRY ) + if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & (1 << FACILITY_INDUSTRY) ) { if( wcslen( sFacilitiesString ) == 0 ) { @@ -59,13 +105,13 @@ void GetSectorFacilitiesFlags( INT16 sMapX, INT16 sMapY, STR16 sFacilitiesString } else { - wcscat( sFacilitiesString, L","); + wcscat( sFacilitiesString, L", "); wcscat( sFacilitiesString, sFacilitiesStrings[ 2 ]); } } // prison - if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & SFCF_PRISON ) + if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & (1 << FACILITY_PRISON) ) { if( wcslen( sFacilitiesString ) == 0 ) { @@ -73,13 +119,27 @@ void GetSectorFacilitiesFlags( INT16 sMapX, INT16 sMapY, STR16 sFacilitiesString } else { - wcscat( sFacilitiesString, L","); + wcscat( sFacilitiesString, L", "); wcscat( sFacilitiesString, sFacilitiesStrings[ 3 ]); } } + // HEADROCK HAM 3.4: What about the military base! + if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & (1 << FACILITY_MILITARY) ) + { + if( wcslen( sFacilitiesString ) == 0 ) + { + swprintf( sFacilitiesString, L"%s", sFacilitiesStrings[ 4 ] ); + } + else + { + wcscat( sFacilitiesString, L", "); + wcscat( sFacilitiesString, sFacilitiesStrings[ 4 ]); + } + } + // airport - if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & SFCF_AIRPORT ) + if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & (1 << FACILITY_AIRPORT) ) { if( wcslen( sFacilitiesString ) == 0 ) { @@ -87,13 +147,13 @@ void GetSectorFacilitiesFlags( INT16 sMapX, INT16 sMapY, STR16 sFacilitiesString } else { - wcscat( sFacilitiesString, L","); + wcscat( sFacilitiesString, L", "); wcscat( sFacilitiesString, sFacilitiesStrings[ 5 ]); } } // gun range - if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & SFCF_GUN_RANGE ) + if( SectorInfo[ SECTOR( sMapX, sMapY ) ].uiFacilitiesFlags & (1 << FACILITY_GUNRANGE) ) { if( wcslen( sFacilitiesString ) == 0 ) { @@ -101,12 +161,11 @@ void GetSectorFacilitiesFlags( INT16 sMapX, INT16 sMapY, STR16 sFacilitiesString } else { - wcscat( sFacilitiesString, L","); + wcscat( sFacilitiesString, L", "); wcscat( sFacilitiesString, sFacilitiesStrings[ 6 ]); } } - - sFacilitiesString[ wcslen( sFacilitiesString ) ] = 0; + */ return; } diff --git a/Strategic/Player Command.h b/Strategic/Player Command.h index 21734ed8..c0da116a 100644 --- a/Strategic/Player Command.h +++ b/Strategic/Player Command.h @@ -6,7 +6,7 @@ // build main facilities strings for sector -void GetSectorFacilitiesFlags( INT16 sMapX, INT16 sMapY, STR16 sFacilitiesString ); +void AddFacilitiesToBox( INT16 sMapX, INT16 sMapY, UINT32 *uiHandle, BOOLEAN fCityInfoBox ); // set sector as enemy controlled BOOLEAN SetThisSectorAsEnemyControlled( INT16 sMapX, INT16 sMapY, INT8 bMapZ, BOOLEAN fContested ); diff --git a/Strategic/Queen Command.cpp b/Strategic/Queen Command.cpp index 6b03b487..5686044f 100644 --- a/Strategic/Queen Command.cpp +++ b/Strategic/Queen Command.cpp @@ -210,15 +210,15 @@ UINT8 NumEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) GROUP *pGroup; UINT8 ubNumTroops; - // HEADROCK: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, - // probably isn't. But I need this bit to work. - if (sSectorX < MINIMUM_VALID_X_COORDINATE || - sSectorX > MAXIMUM_VALID_X_COORDINATE || - sSectorY < MINIMUM_VALID_Y_COORDINATE || - sSectorY > MAXIMUM_VALID_Y_COORDINATE) - { - return (0); - } + // HEADROCK HAM 3.5: This is a TEMPORARY fix to avoid the assertion error. Not sure this is the best solution, + // probably isn't. But I need this bit to work. + if (sSectorX < MINIMUM_VALID_X_COORDINATE || + sSectorX > MAXIMUM_VALID_X_COORDINATE || + sSectorY < MINIMUM_VALID_Y_COORDINATE || + sSectorY > MAXIMUM_VALID_Y_COORDINATE) + { + return (0); + } AssertGE( sSectorX, MINIMUM_VALID_X_COORDINATE); AssertLE( sSectorX, MAXIMUM_VALID_X_COORDINATE ); @@ -503,8 +503,7 @@ BOOLEAN PrepareEnemyForSectorBattle() // They arrived in multiple groups, so here they come pThisSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ]; - GenerateDirectionInfos( gWorldSectorX, gWorldSectorY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( gWorldSectorX, gWorldSectorY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( gWorldSectorX, gWorldSectorY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( unsigned ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) { @@ -788,7 +787,6 @@ BOOLEAN PrepareEnemyForSectorBattle() continue; } - // At this point we should not have added more soldiers than are in slots AssertGT( sNumSlots, 0 ); switch( pSoldier->ubSoldierClass ) @@ -1394,6 +1392,7 @@ void AddPossiblePendingEnemiesToBattle() if( ubStrategicInsertionCode == 255 ) { + // HEADROCK HAM 3.5: This runs into assertion errors along the map's edge! Should be fixed! if( NumEnemiesInSector( gWorldSectorX + 1, gWorldSectorY ) ) ubStrategicInsertionCode = INSERTION_CODE_EAST; else if( NumEnemiesInSector( gWorldSectorX - 1, gWorldSectorY ) ) @@ -1704,6 +1703,11 @@ void AddEnemiesToBattle( GROUP *pGroup, UINT8 ubStrategicInsertionCode, UINT8 ub } UpdateMercInSector( pSoldier, gWorldSectorX, gWorldSectorY, 0 ); } + // HEADROCK HAM 3.2: enemy reinforcements arrive with 0 APs. + if (gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 1 || gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 2) + { + pSoldier->bActionPoints = 0; + } } } diff --git a/Strategic/Quests.cpp b/Strategic/Quests.cpp index 7f92b8c9..44a6b42b 100644 --- a/Strategic/Quests.cpp +++ b/Strategic/Quests.cpp @@ -1126,8 +1126,9 @@ BOOLEAN CheckFact( UINT16 usFact, UINT8 ubProfileID ) gubFact[usFact] = gMercProfiles[ ARNIE ].bMercStatus != MERC_IS_DEAD; break; - case FACT_I16_BLOODCATS_KILLED: - gubFact[usFact] = (SectorInfo[ SEC_I16 ].bBloodCats == 0); + // HEADROCK HAM 3.6: No longer necessarily I16. + case FACT_BLOODCAT_LAIR_CLEANED: + gubFact[usFact] = (SectorInfo[ gubBloodcatLairSectorId ].bBloodCats == 0); break; case FACT_NPC_BANDAGED_TODAY: diff --git a/Strategic/Quests.h b/Strategic/Quests.h index 75b47324..a66ce9b1 100644 --- a/Strategic/Quests.h +++ b/Strategic/Quests.h @@ -430,7 +430,8 @@ enum Facts FACT_KINGPIN_NOT_IN_OFFICE,// 256 FACT_DONT_OWE_KINGPIN_MONEY,// 257 FACT_PC_MARRYING_DARYL_IS_FLO,// 258 - FACT_I16_BLOODCATS_KILLED,// 259 + // HEADROCK HAM 3.6: Renamed this. It is no longer necessarily at I16. + FACT_BLOODCAT_LAIR_CLEANED,// 259 FACT_NPC_COWERING = 261, diff --git a/Strategic/Reinforcement.cpp b/Strategic/Reinforcement.cpp index 64a9a975..38f603ad 100644 --- a/Strategic/Reinforcement.cpp +++ b/Strategic/Reinforcement.cpp @@ -52,8 +52,7 @@ void GetNumberOfEnemiesInFiveSectors( INT16 sSectorX, INT16 sSectorY, UINT8 *pub if ( ( GetTownIdForSector( sSectorX, sSectorY ) == OMERTA )&&( gGameOptions.ubDifficultyLevel != DIF_LEVEL_INSANE ) ) //Madd: skip Omerta //Lal: but not on insane ;-) return; - GenerateDirectionInfos( sSectorX, sSectorY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sSectorX, sSectorY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sSectorX, sSectorY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) { @@ -104,8 +103,7 @@ BOOLEAN IsGroupInARightSectorToReinforce( GROUP *pGroup, INT16 sSectorX, INT16 s if( !gGameExternalOptions.gfAllowReinforcements ) return FALSE; - GenerateDirectionInfos( sSectorX, sSectorY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sSectorX, sSectorY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sSectorX, sSectorY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) if( pGroup->ubSectorX == SECTORX( pusMoveDir[ ubIndex ][ 0 ] ) && pGroup->ubSectorY == SECTORY( pusMoveDir[ ubIndex ][ 0 ] ) ) @@ -120,8 +118,7 @@ UINT8 GetAdjacentSectors( UINT8 pSectors[4], INT16 sSectorX, INT16 sSectorY ) UINT8 ubDirNumber = 0, ubIndex; UINT8 ubCounter = 0; - GenerateDirectionInfos( sSectorX, sSectorY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sSectorX, sSectorY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sSectorX, sSectorY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) pSectors[ ubCounter++ ] = (UINT8)pusMoveDir[ ubIndex ][ 0 ]; @@ -140,8 +137,7 @@ UINT8 CountAllMilitiaInFiveSectors(INT16 sMapX, INT16 sMapY) if( !gGameExternalOptions.gfAllowReinforcements ) return ubResult; - GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) ubResult += CountAllMilitiaInSector( SECTORX( pusMoveDir[ ubIndex ][ 0 ] ), SECTORY( pusMoveDir[ ubIndex ][ 0 ] ) ); @@ -161,8 +157,7 @@ UINT8 MilitiaInFiveSectorsOfRank( INT16 sMapX, INT16 sMapY, UINT8 ubRank ) if( !gGameExternalOptions.gfAllowReinforcements ) return ubResult; - GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) ubResult += MilitiaInSectorOfRank( SECTORX( pusMoveDir[ ubIndex ][ 0 ] ), SECTORY( pusMoveDir[ ubIndex ][ 0 ] ), ubRank ); @@ -179,12 +174,11 @@ BOOLEAN ARMoveBestMilitiaManFromAdjacentSector(INT16 sMapX, INT16 sMapY) if( !gGameExternalOptions.gfAllowReinforcements ) return FALSE; - if( CountAllMilitiaInSector( sMapX, sMapY ) >= gGameExternalOptions.guiMaxMilitiaSquadSize || + if( CountAllMilitiaInSector( sMapX, sMapY ) >= gGameExternalOptions.iMaxMilitiaPerSector || CountAllMilitiaInFiveSectors( sMapX, sMapY ) - CountAllMilitiaInSector( sMapX, sMapY ) == 0 ) return FALSE; - GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); ubRandom = Random( ubDirNumber ); @@ -209,8 +203,7 @@ BOOLEAN ARRemoveMilitiaMan( INT16 sMapX, INT16 sMapY, UINT8 ubRank ) if( !gGameExternalOptions.gfAllowReinforcements ) return FALSE; - GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ; ; ) { @@ -256,8 +249,7 @@ UINT8 DoReinforcementAsPendingEnemy( INT16 sMapX, INT16 sMapY ) pThisSector = &SectorInfo[ SECTOR( sMapX, sMapY ) ]; - GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); for( ubIndex = 0; ubIndex < ubDirNumber; ubIndex++ ) { @@ -328,7 +320,7 @@ UINT8 NumFreeMilitiaSlots() if( !pSoldier->bActive ) ubNumFreeSlots++; } - return max( 0 , ubNumFreeSlots - ( gGameExternalOptions.ubGameMaximumNumberOfRebels - gGameExternalOptions.guiMaxMilitiaSquadSize ) ); + return max( 0 , ubNumFreeSlots - ( gGameExternalOptions.ubGameMaximumNumberOfRebels - gGameExternalOptions.iMaxMilitiaPerSector ) ); } UINT8 DoReinforcementAsPendingMilitia( INT16 sMapX, INT16 sMapY, UINT8 *pubRank ) @@ -342,8 +334,7 @@ UINT8 DoReinforcementAsPendingMilitia( INT16 sMapX, INT16 sMapY, UINT8 *pubRank // pThisSector = &SectorInfo[ SECTOR( sMapX, sMapY ) ]; - GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, - ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ? TRUE : FALSE ), TRUE, IS_ONLY_IN_CITIES ); + GenerateDirectionInfos( sMapX, sMapY, &ubDirNumber, pusMoveDir, FALSE, TRUE ); if( CountAllMilitiaInFiveSectors( sMapX, sMapY ) - CountAllMilitiaInSector( sMapX, sMapY ) == 0 ) { diff --git a/Strategic/Strategic AI.cpp b/Strategic/Strategic AI.cpp index 1d975043..01978667 100644 --- a/Strategic/Strategic AI.cpp +++ b/Strategic/Strategic AI.cpp @@ -3704,6 +3704,9 @@ BOOLEAN LoadStrategicAI( HWFILE hFile ) } if( ubSAIVersion < 18 ) { //adjust down the number of bloodcats based on difficulty in the two special bloodcat levels + // HEADROCK HAM 3.6: Run the Init function instead. + InitBloodCatSectors(); + /* switch( gGameOptions.ubDifficultyLevel ) { case DIF_LEVEL_EASY: //50% @@ -3729,7 +3732,7 @@ BOOLEAN LoadStrategicAI( HWFILE hFile ) SectorInfo[ SEC_I16 ].bBloodCats = 39; SectorInfo[ SEC_N5 ].bBloodCats = 37; break; - } + }*/ } if( ubSAIVersion < 19 ) { @@ -3753,6 +3756,8 @@ BOOLEAN LoadStrategicAI( HWFILE hFile ) } if( ubSAIVersion < 22 ) { //adjust down the number of bloodcats based on difficulty in the two special bloodcat levels + // HEADROCK HAM 3.6: Run the Init function instead. + InitBloodCatSectors(); switch( gGameOptions.ubDifficultyLevel ) { case DIF_LEVEL_EASY: //50% diff --git a/Strategic/Strategic Merc Handler.cpp b/Strategic/Strategic Merc Handler.cpp index 32d3f503..e2902a5c 100644 --- a/Strategic/Strategic Merc Handler.cpp +++ b/Strategic/Strategic Merc Handler.cpp @@ -42,6 +42,10 @@ #include "Quests.h" // HEADROCK HAM B1: Additional Include for Dynamic Roaming Restrictions #include "MilitiaSquads.h" + // HEADROCK HAM 3.6: Additional include for Facility Operating Deby + #include "Facilities.h" + // HEADROCK HAM 3.6: And another include, for militia upkeep costs. + #include "Town Militia.h" #endif @@ -513,10 +517,19 @@ void MercDailyUpdate() // rebuild list for mapscreen ReBuildCharactersList( ); // HEADROCK HAM B1: Run a function to redefine Roaming Militia Restrictions. - if (gGameExternalOptions.bDynamicRestrictRoaming) + if (gGameExternalOptions.fDynamicRestrictRoaming) { AdjustRoamingRestrictions(); } + + // HEADROCK HAM 3.6: Pay debt for operating Facilities today. If can't be paid, apply loyalty hit. + if (giTotalOwedForFacilityOperationsToday) + { + HandleDailyPaymentFacilityDebt(); + } + + // HEADROCK HAM 3.6: Pay for militia upkeep + HandleMilitiaUpkeepPayment(); } /* diff --git a/Strategic/Strategic Mines.cpp b/Strategic/Strategic Mines.cpp index f474f22b..0504fcf3 100644 --- a/Strategic/Strategic Mines.cpp +++ b/Strategic/Strategic Mines.cpp @@ -21,6 +21,7 @@ #include "Strategic AI.h" #include "Campaign Types.h" #include "history.h" + #include "Facilities.h" #endif @@ -202,12 +203,27 @@ void InitializeMines( void ) // Alma mine can't run out for quest-related reasons (see Ian) } while (gMineStatus[ubDepletedMineIndex].fEmpty || (ubDepletedMineIndex == MINE_ALMA)); + // HEADROCK HAM 3.1: We can now select which mine runs out, or disable mine shutdown. + if (gGameExternalOptions.fManuallySelectMineShutdown) + { + // Make sure selection isn't 0 (no mine runs out) or invalid (San Mona, Alma) + if (gGameExternalOptions.ubWhichMineRunsOut > 0 && + gGameExternalOptions.ubWhichMineRunsOut < MAX_NUMBER_OF_MINES && + gGameExternalOptions.ubWhichMineRunsOut-1 != MINE_SAN_MONA && + gGameExternalOptions.ubWhichMineRunsOut-1 != MINE_ALMA ) + { + // Set depleted mine index. + ubDepletedMineIndex = (gGameExternalOptions.ubWhichMineRunsOut-1); + } + } for( ubMineIndex = 0; ubMineIndex < MAX_NUMBER_OF_MINES; ubMineIndex++ ) { pMineStatus = &(gMineStatus[ ubMineIndex ]); - if (ubMineIndex == ubDepletedMineIndex) + // HEADROCK HAM 3.1: We can disable mine shutdown, so make sure the settings aren't telling us to do so. + if (!(gGameExternalOptions.fManuallySelectMineShutdown && gGameExternalOptions.ubWhichMineRunsOut == 0) && + (ubMineIndex == ubDepletedMineIndex)) { if ( ubDepletedMineIndex == MINE_DRASSEN ) { @@ -343,7 +359,7 @@ UINT32 GetMaxPeriodicRemovalFromMine( INT8 bMineIndex ) } // HEADROCK HAM B1: Affected by external INI option. - return(( gMineStatus[ bMineIndex ].uiMaxRemovalRate * gGameExternalOptions.iMineIncomePercentage ) / 100); + return(( gMineStatus[ bMineIndex ].uiMaxRemovalRate * gGameExternalOptions.usMineIncomePercentage ) / 100); } @@ -370,7 +386,7 @@ UINT32 GetMaxDailyRemovalFromMine( INT8 bMineIndex ) uiAmtExtracted = gMineStatus[ bMineIndex ].uiRemainingOreSupply; } // HEADROCK HAM B1: Affected by external INI option. - uiAmtExtracted = (uiAmtExtracted * gGameExternalOptions.iMineIncomePercentage) / 100; + uiAmtExtracted = (uiAmtExtracted * gGameExternalOptions.usMineIncomePercentage) / 100; return(uiAmtExtracted); } @@ -660,15 +676,22 @@ void HandleIncomeFromMines( void ) { INT32 iIncome = 0; INT8 bCounter = 0; + // HEADROCK HAM 3.6: Modifier from Facilities + INT32 iFacilityModifier = 0; // mine each mine, check if we own it and such for( bCounter = 0; bCounter < MAX_NUMBER_OF_MINES; bCounter++ ) { + if (bCounter) + { + // HEADROCK HAM 3.6: Add facility modifier as a percentage + iFacilityModifier = 100 + MineIncomeModifierFromFacility( bCounter ); + } // mine this mine - iIncome += MineAMine( bCounter ); + iIncome += (MineAMine( bCounter ) * iFacilityModifier) / 100; } // HEADROCK HAM B1: Affected by external INI Option. - iIncome = (iIncome * gGameExternalOptions.iMineIncomePercentage) / 100; + iIncome = (iIncome * gGameExternalOptions.usMineIncomePercentage) / 100; if( iIncome ) { AddTransactionToPlayersBook( DEPOSIT_FROM_SILVER_MINE, 0, GetWorldTotalMin( ), iIncome ); @@ -681,6 +704,8 @@ UINT32 PredictDailyIncomeFromAMine( INT8 bMineIndex ) // predict income from this mine, estimate assumes mining situation will not change during next 4 income periods // (miner loyalty, % town controlled, monster infestation level, and current max removal rate may all in fact change) UINT32 uiAmtExtracted = 0; + // HEADROCK HAM 3.6: Facilities can now modify income + INT32 iFacilityModifier = 0; if(PlayerControlsMine(bMineIndex)) { @@ -693,10 +718,16 @@ UINT32 PredictDailyIncomeFromAMine( INT8 bMineIndex ) // yes reduce to value of mine uiAmtExtracted = gMineStatus[ bMineIndex ].uiRemainingOreSupply; } + if (bMineIndex) + { + // HEADROCK HAM 3.6: Facility modifier applied as a percentage + iFacilityModifier = 100 + MineIncomeModifierFromFacility( bMineIndex ); + uiAmtExtracted = (uiAmtExtracted * iFacilityModifier) / 100; + } } // HEADROCK HAM B1: Affected by external INI Option. - return( ( uiAmtExtracted * gGameExternalOptions.iMineIncomePercentage ) / 100 ) ; + return( ( uiAmtExtracted * gGameExternalOptions.usMineIncomePercentage ) / 100 ) ; } @@ -727,7 +758,7 @@ INT32 CalcMaxPlayerIncomeFromMines( void ) iTotal += (MINE_PRODUCTION_NUMBER_OF_PERIODS * gMineStatus[bCounter].uiMaxRemovalRate); } // HEADROCK HAM B1: Affected by external INI Option. - iTotal = (iTotal * gGameExternalOptions.iMineIncomePercentage) / 100; + iTotal = (iTotal * gGameExternalOptions.usMineIncomePercentage) / 100; return( iTotal ); } diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index 6f4ad398..ca4a1cb4 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -1554,6 +1554,10 @@ void AwardExperienceForTravelling( GROUP * pGroup ) void AddCorpsesToBloodcatLair( INT16 sSectorX, INT16 sSectorY ) { + // HEADROCK HAM 3.6: Note, lair location externalized, but may cause issues because the GridNos here are + // hardcoded... Maybe there will be a better way to solve this in the future, with externalized GridNos. + // At the moment, no changes. + ROTTING_CORPSE_DEFINITION Corpse; INT16 sXPos, sYPos; @@ -1884,12 +1888,14 @@ void GroupArrivedAtSector( UINT8 ubGroupID, BOOLEAN fCheckForBattle, BOOLEAN fNe if( pGroup->pPlayerList->pSoldier->bAssignment < ON_DUTY ) { // squad - ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pAssignmentStrings[ pGroup->pPlayerList->pSoldier->bAssignment ], pMapVertIndex[ pGroup->pPlayerList->pSoldier->sSectorY ], pMapHortIndex[ pGroup->pPlayerList->pSoldier->sSectorX ]); + // HEADROCK HAM 3.6: Messages are no longer yellow by default. + ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pAssignmentStrings[ pGroup->pPlayerList->pSoldier->bAssignment ], pMapVertIndex[ pGroup->pPlayerList->pSoldier->sSectorY ], pMapHortIndex[ pGroup->pPlayerList->pSoldier->sSectorX ]); } else { // a loner - ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pGroup->pPlayerList->pSoldier->name, pMapVertIndex[ pGroup->pPlayerList->pSoldier->sSectorY ], pMapHortIndex[ pGroup->pPlayerList->pSoldier->sSectorX ] ); + // HEADROCK HAM 3.6: Messages are no longer yellow by default. + ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pGroup->pPlayerList->pSoldier->name, pMapVertIndex[ pGroup->pPlayerList->pSoldier->sSectorY ], pMapHortIndex[ pGroup->pPlayerList->pSoldier->sSectorX ] ); } } } @@ -1978,7 +1984,8 @@ void GroupArrivedAtSector( UINT8 ubGroupID, BOOLEAN fCheckForBattle, BOOLEAN fNe // don't print any messages when arriving underground, there's no delay involved if ( GroupAtFinalDestination( pGroup ) && ( pGroup->ubSectorZ == 0 ) && !fNeverLeft ) { - ScreenMsg( FONT_MCOLOR_DKRED, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pVehicleStrings[ pVehicleList[ iVehId ].ubVehicleType ], pMapVertIndex[ pGroup->ubSectorY ], pMapHortIndex[ pGroup->ubSectorX ] ); + // HEADROCK HAM 3.6: Messages are no longer yellow by default. + ScreenMsg( FONT_MCOLOR_LTGREEN, MSG_INTERFACE, pMessageStrings[ MSG_ARRIVE ], pVehicleStrings[ pVehicleList[ iVehId ].ubVehicleType ], pMapVertIndex[ pGroup->ubSectorY ], pMapHortIndex[ pGroup->ubSectorX ] ); } } } @@ -1997,7 +2004,8 @@ void GroupArrivedAtSector( UINT8 ubGroupID, BOOLEAN fCheckForBattle, BOOLEAN fNe if ( !pGroup->fVehicle || !IsGroupTheHelicopterGroup( pGroup ) ) { // ATE: Add a few corpse to the bloodcat lair... - if ( SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) == SEC_I16 && fFirstTimeInSector ) + // HEADROCK: No longer hardcoded to I16 + if ( SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ) == gubBloodcatLairSectorId && fFirstTimeInSector ) { AddCorpsesToBloodcatLair( pGroup->ubSectorX, pGroup->ubSectorY ); } @@ -4708,13 +4716,20 @@ void RandomizePatrolGroupLocation( GROUP *pGroup ) //roll the dice to see if this will become an ambush random encounter. BOOLEAN TestForBloodcatAmbush( GROUP *pGroup ) { + /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // HEADROCK HAM 3.6: Ambush locations, min/max strength and chance of ambush have all been externalized to XML. + SECTORINFO *pSector; INT32 iHoursElapsed; UINT8 ubSectorID; UINT8 ubChance; - INT8 bDifficultyMaxCats; - INT8 bProgressMaxCats; + UINT8 ubDifficulty = gGameOptions.ubDifficultyLevel-1; // For use by Placements Array. Novice = 0 instead of 1. + UINT8 ubRange; + UINT8 ubRandomCats; INT8 bNumMercMaxCats; + UINT8 PlacementType; // 0 = Ambush site. 1 = Static placement. 2 = Lair. + UINT8 ubMinBloodcats; // XML minimum. + UINT8 ubMaxBloodcats; // XML maximum. BOOLEAN fAlreadyAmbushed = FALSE; if( pGroup->ubSectorZ ) @@ -4725,17 +4740,24 @@ BOOLEAN TestForBloodcatAmbush( GROUP *pGroup ) ubSectorID = (UINT8)SECTOR( pGroup->ubSectorX, pGroup->ubSectorY ); pSector = &SectorInfo[ ubSectorID ]; - ubChance = 5 * gGameOptions.ubDifficultyLevel; + // Read chance from XML data, based on difficulty. + ubChance = gBloodcatPlacements[ ubSectorID ][ ubDifficulty ].ubAmbushChance; + iHoursElapsed = __max(0,(GetWorldTotalMin() - pSector->uiTimeCurrentSectorWasLastLoaded) / 60); + // Decrease chance to 0% if sector has been visited this hour. Every hour after that, increase the chance by 1% + // until it reaches the default value again. + ubChance = (UINT8)__min((INT32)ubChance, iHoursElapsed); + ubChance = __max(0,ubChance); - iHoursElapsed = (GetWorldTotalMin() - pSector->uiTimeCurrentSectorWasLastLoaded) / 60; - if( ubSectorID == SEC_N5 || ubSectorID == SEC_I16 ) - { //These are special maps -- we use all placements. - if( pSector->bBloodCats == -1 ) - { - pSector->bBloodCats = pSector->bBloodCatPlacements; - } - else if( pSector->bBloodCats > 0 && pSector->bBloodCats < pSector->bBloodCatPlacements ) - { //Slowly have them recuperate if we haven't been here for a long time. The population will + // Determine what type of placement this is. + PlacementType = gBloodcatPlacements[ ubSectorID ][0].PlacementType; + + // At lair? + if( PlacementType == BLOODCAT_PLACEMENT_LAIR ) + { + // Bloodcats still exist here, but aren't at the optimal number? + if( pSector->bBloodCats > 0 && pSector->bBloodCats < pSector->bBloodCatPlacements ) + { + //Slowly have them recuperate if we haven't been here for a long time. The population will //come back up to the maximum if left long enough. INT32 iBloodCatDiff; iBloodCatDiff = pSector->bBloodCatPlacements - pSector->bBloodCats; @@ -4743,55 +4765,80 @@ BOOLEAN TestForBloodcatAmbush( GROUP *pGroup ) } //Once 0, the bloodcats will never recupe. } - else if( pSector->bBloodCats == -1 ) - { //If we haven't been ambushed by bloodcats yet... - if( gfAutoAmbush || PreChance( ubChance ) ) + + // At ambush sector? + else if( PlacementType == BLOODCAT_PLACEMENT_AMBUSH ) + { + //If there's any chance of ambush in this sector, and we beat that chance... + if( ubChance && (gfAutoAmbush || PreChance( ubChance )) ) { //randomly choose from 5-8, 7-10, 9-12 bloodcats based on easy, normal, and hard, respectively - bDifficultyMaxCats = (INT8)( Random( 4 ) + gGameOptions.ubDifficultyLevel*2 + 3 ); - + //bDifficultyMaxCats = (INT8)( Random( 4 ) + gGameOptions.ubDifficultyLevel*2 + 3 ); //maximum of 3 bloodcats or 1 for every 6%, 5%, 4% progress based on easy, normal, and hard, respectively - bProgressMaxCats = (INT8)max( CurrentPlayerProgressPercentage() / (7 - gGameOptions.ubDifficultyLevel), 3 ); - - //make sure bloodcats don't outnumber mercs by a factor greater than 2 - bNumMercMaxCats = (INT8)(PlayerMercsInSector( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ) * 2); - + //bProgressMaxCats = (INT8)max( CurrentPlayerProgressPercentage() / (7 - gGameOptions.ubDifficultyLevel), 3 ); //choose the lowest number of cats calculated by difficulty and progress. - pSector->bBloodCats = (INT8)min( bDifficultyMaxCats, bProgressMaxCats ); + //pSector->bBloodCats = (INT8)min( bDifficultyMaxCats, bProgressMaxCats ); + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // HEADROCK HAM 3.6: Minimum and Maximum are now modder-defined, so pick a random number between them. + + // Read Min/Max from XML + ubMaxBloodcats = gBloodcatPlacements[ ubSectorID ][ ubDifficulty ].ubMaxBloodcats; + ubMinBloodcats = gBloodcatPlacements[ ubSectorID ][ ubDifficulty ].ubMinBloodcats; + // Find range between Min and Max + ubRange = ubMaxBloodcats-ubMinBloodcats; + // Adjust range based on current progress. + ubRange = __max(0,( ubRange * CurrentPlayerProgressPercentage() ) / 100); + // Randomize "extra" bloodcats + ubRandomCats = PreRandom(ubRange+1); + // Add "extra" bloodcats to minimum value + ubRandomCats += ubMinBloodcats; + + // SET NUMBER OF CATS THAT WILL APPEAR + pSector->bBloodCats = ubRandomCats; + if( gGameOptions.ubDifficultyLevel < DIF_LEVEL_HARD ) - { //if not hard difficulty, ensure cats never outnumber mercs by a factor of 2 (min 3 bloodcats) + { + //At NOVICE/EXPERIENCED difficulty, ensure cats never outnumber mercs by a factor of more than 2. + bNumMercMaxCats = (INT8)(PlayerMercsInSector( pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubSectorZ ) * 2); pSector->bBloodCats = (INT8)min( pSector->bBloodCats, bNumMercMaxCats ); - pSector->bBloodCats = (INT8)max( pSector->bBloodCats, 3 ); + //pSector->bBloodCats = (INT8)max( pSector->bBloodCats, 3 ); } //ensure that there aren't more bloodcats than placements pSector->bBloodCats = (INT8)min( pSector->bBloodCats, pSector->bBloodCatPlacements ); + + // Once again, make absolutely sure we're still within XML-set limits + pSector->bBloodCats = (INT8)min( pSector->bBloodCats, ubMaxBloodcats); + pSector->bBloodCats = (INT8)max( pSector->bBloodCats, ubMinBloodcats); } - } - else if( ubSectorID != SEC_I16 ) - { - if( !gfAutoAmbush && PreChance( 95 ) ) - { //already ambushed here. But 5% chance of getting ambushed again! + else + { + // Chance was 0. fAlreadyAmbushed = TRUE; } } - if( !fAlreadyAmbushed && ubSectorID != SEC_N5 && pSector->bBloodCats > 0 && + // An ambush is occuring? + if( !fAlreadyAmbushed && PlacementType != BLOODCAT_PLACEMENT_STATIC && pSector->bBloodCats > 0 && !pGroup->fVehicle && !NumEnemiesInSector( pGroup->ubSectorX, pGroup->ubSectorY ) ) { - if( ubSectorID != SEC_I16 || !gubFact[ FACT_PLAYER_KNOWS_ABOUT_BLOODCAT_LAIR ] ) + // Wilderness ambush, or the player has unwittingly entered the lair? + if( PlacementType == BLOODCAT_PLACEMENT_AMBUSH || !gubFact[ FACT_PLAYER_KNOWS_ABOUT_BLOODCAT_LAIR ] ) { + // Ambush occurs. gubEnemyEncounterCode = BLOODCAT_AMBUSH_CODE; } else { + // Player knowingly enters lair. gubEnemyEncounterCode = ENTERING_BLOODCAT_LAIR_CODE; } return TRUE; } else { + // No special bloodcat encounter. This ALWAYS happens in STATIC PLACEMENT sectors. gubEnemyEncounterCode = NO_ENCOUNTER_CODE; return FALSE; } @@ -4808,7 +4855,8 @@ void NotifyPlayerOfBloodcatBattle( UINT8 ubSectorX, UINT8 ubSectorY ) } else if( gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE ) { - wcscpy( str, pMapErrorString[ 13 ] ); + GetSectorIDString( ubSectorX, ubSectorY, 0, zTempString, FALSE ); + swprintf( str, pMapErrorString[ 13 ], zTempString ); } if( guiCurrentScreen == MAP_SCREEN ) diff --git a/Strategic/Strategic Town Loyalty.cpp b/Strategic/Strategic Town Loyalty.cpp index ede8e7af..9440d303 100644 --- a/Strategic/Strategic Town Loyalty.cpp +++ b/Strategic/Strategic Town Loyalty.cpp @@ -35,6 +35,8 @@ #include "Strategic Status.h" // HEADROCK HAM B1: Added include for Dynamic Roaming Militia #include "MilitiaSquads.h" + // HEADROCK HAM 3.6: Include for Facility Debt + #include "Facilities.h" #endif // the max loyalty rating for any given town @@ -1817,7 +1819,7 @@ 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) + if (gGameExternalOptions.fDynamicRestrictRoaming) { AdjustRoamingRestrictions(); } @@ -1994,3 +1996,24 @@ void MaximizeLoyaltyForDeidrannaKilled(void) SetTownLoyalty(bTownId, 100); } } + +// HEADROCK HAM 3.6: A loyalty hit based on outstanding debt for facility use. +void HandleFacilityDebtLoyaltyHit( void ) +{ + // Loyalty hit is 1% per $1000 dollars owed. + INT32 iLoyaltyHit = giTotalOwedForFacilityOperationsToday; + + if (giTotalOwedForFacilityOperationsToday > 500) + { + // But no less than 1%, if $500 are owed. + iLoyaltyHit = __max(500,iLoyaltyHit); + } + + if (iLoyaltyHit > 1) + { + DecrementTownLoyaltyEverywhere( iLoyaltyHit ); + } +} + + + diff --git a/Strategic/Strategic Town Loyalty.h b/Strategic/Strategic Town Loyalty.h index 2662ed44..273aeea9 100644 --- a/Strategic/Strategic Town Loyalty.h +++ b/Strategic/Strategic Town Loyalty.h @@ -204,4 +204,7 @@ void HandleLoyaltyImplicationsOfMercRetreat( INT8 bRetreatCode, INT16 sSectorX, void MaximizeLoyaltyForDeidrannaKilled( void ); +// HEADROCK HAM 3.6: Loyalty hit for owing too much money on facility work +void HandleFacilityDebtLoyaltyHit( void ); + #endif \ No newline at end of file diff --git a/Strategic/Strategic_2005Express.vcproj b/Strategic/Strategic_2005Express.vcproj index 87304c6b..7b119c7f 100644 --- a/Strategic/Strategic_2005Express.vcproj +++ b/Strategic/Strategic_2005Express.vcproj @@ -296,6 +296,10 @@ RelativePath=".\Creature Spreading.h" > + + @@ -470,6 +474,10 @@ RelativePath=".\Creature Spreading.cpp" > + + @@ -626,14 +634,38 @@ RelativePath=".\XML_Army.cpp" > + + + + + + + + + + + + diff --git a/Strategic/Strategic_VS2008.vcproj b/Strategic/Strategic_VS2008.vcproj index 05177f4a..c7d8f2e5 100644 --- a/Strategic/Strategic_VS2008.vcproj +++ b/Strategic/Strategic_VS2008.vcproj @@ -295,6 +295,10 @@ RelativePath="Creature Spreading.h" > + + @@ -467,6 +471,10 @@ RelativePath="Creature Spreading.cpp" > + + @@ -623,15 +631,39 @@ RelativePath="XML_Army.cpp" > + + + + + + + + + + + + + + - - diff --git a/Strategic/Town Militia.cpp b/Strategic/Town Militia.cpp index 2a095a89..bb19438b 100644 --- a/Strategic/Town Militia.cpp +++ b/Strategic/Town Militia.cpp @@ -31,6 +31,13 @@ #include "Map Screen Interface Map.h" #endif +// HEADROCK HAM 3: include these files so that a militia trainer's Effective Leadership can be determined. Used +// to determine the number of militia trained by this merc per session. In the future may also determine QUALITY +// of trained troops. +#include "SkillCheck.h" +#include "Soldier Control.h" +#include "soldier profile type.h" + #include "MilitiaSquads.h" #define SIZE_OF_MILITIA_COMPLETED_TRAINING_LIST 50 @@ -76,28 +83,35 @@ BOOLEAN gfMilitiaAllowedInTown[MAX_TOWNS] = // private prototypes void PayMilitiaTrainingYesNoBoxCallback( UINT8 bExitValue ); +// HEADROCK HAM 3.6: Duplicate function for handling callback on Mobile Militia training +void PayMobileMilitiaTrainingYesNoBoxCallback( UINT8 bExitValue ); void CantTrainMilitiaOkBoxCallback( UINT8 bExitValue ); -void MilitiaTrainingRejected( void ); +void CantTrainMobileMilitiaOkBoxCallback( UINT8 bExitValue ); + +void PayForTrainingInSector( UINT8 ubSector ); void InitFriendlyTownSectorServer(UINT8 ubTownId, INT16 sSkipSectorX, INT16 sSkipSectorY); BOOLEAN ServeNextFriendlySectorInTown( INT16 *sNeighbourX, INT16 *sNeighbourY ); -void BuildListOfUnpaidTrainableSectors( void ); -INT32 GetNumberOfUnpaidTrainableSectors( void ); -void ContinueTrainingInThisSector( void ); -void StartTrainingInAllUnpaidTrainableSectors(); -void PayForTrainingInSector( UINT8 ubSector ); -void ResetDoneFlagForAllMilitiaTrainersInSector( UINT8 ubSector ); +// HEADROCK HAM 3.6: Instead of adding more functions, I've added an argument that determines whether we're checking +// Garrison Militia payment versus Mobile Militie payment. +void BuildListOfUnpaidTrainableSectors( UINT8 ubMilitiaType ); +INT32 GetNumberOfUnpaidTrainableSectors( UINT8 ubMilitiaType ); +void ContinueTrainingInThisSector( UINT8 ubMilitiaType ); +void StartTrainingInAllUnpaidTrainableSectors( UINT8 ubMilitiaType ); +void ResetDoneFlagForAllMilitiaTrainersInSector( UINT8 ubSector, UINT8 ubMilitiaType ); +void MilitiaTrainingRejected( UINT8 ubMilitiaType ); +// HEADROCK HAM 3.6: Total upkeep costs for keeping militia. +UINT32 guiTotalUpkeepForMilitia = 0; + +// HEADROCK HAM 3.6: Qsort for sectors with militia in them. +int MilitiaListQsortCompare(const void *pArg1, const void *pArg2); #ifdef JA2BETAVERSION void VerifyTownTrainingIsPaidFor( void ); #endif - - - - void TownMilitiaTrainingCompleted( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMapY ) { UINT8 ubMilitiaTrained = 0; @@ -106,7 +120,11 @@ void TownMilitiaTrainingCompleted( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMa UINT8 ubTownId; INT32 iMaxMilitiaPerSector = gGameExternalOptions.iMaxMilitiaPerSector; - INT32 iTrainingSquadSize = gGameExternalOptions.iTrainingSquadSize; + + // HEADROCK HAM 3.6: Leadership may affect the resulting squad size. + UINT8 ubTrainerEffectiveLeadership = FindBestMilitiaTrainingLeadershipInSector ( sMapX, sMapY, pTrainer->bSectorZ, TOWN_MILITIA ); + UINT8 iTrainingSquadSize = __min(iMaxMilitiaPerSector, CalcNumMilitiaTrained(ubTrainerEffectiveLeadership, FALSE)); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia1"); // get town index ubTownId = StrategicMap[ sMapX + sMapY * MAP_WORLD_X ].bNameId; @@ -132,11 +150,16 @@ void TownMilitiaTrainingCompleted( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMa // If we're not training roaming militia, // then we will handle everything as normal. - if( (IsMilitiaTrainableFromSoldiersSectorMaxed( pTrainer, ELITE_MILITIA )) - && (gGameExternalOptions.gfmusttrainroaming) - &&(GetWorldDay( ) >= gGameExternalOptions.guiAllowMilitiaGroupsDelay)) + // HEADROCK HAM 3.6: This is now VOLUNTARY. + if( pTrainer->bAssignment == TRAIN_MOBILE ) { CreateMilitiaSquads(sMapX, sMapY ); + + // the trainer announces to player that he's finished his assignment. Make his sector flash! + AssignmentDone( pTrainer, TRUE, FALSE ); + + // handle completion of town by training group + HandleCompletionOfTownTrainingByGroupWithTrainer( pTrainer, MOBILE_MILITIA ); } else { @@ -298,19 +321,21 @@ void TownMilitiaTrainingCompleted( SOLDIERTYPE *pTrainer, INT16 sMapX, INT16 sMa if( ubTownId != BLANK_SECTOR ) { + // HEADROCK HAM 3.6: Adjust loyalty based on how many militia were trained, compared to + // how many can be trained (max). + UINT32 uiTownLoyaltyBonus = (LOYALTY_BONUS_FOR_TOWN_TRAINING * ubMilitiaTrained) / gGameExternalOptions.iTrainingSquadSize; // loyalty in this town increases a bit because we obviously care about them... - IncrementTownLoyalty( ubTownId, LOYALTY_BONUS_FOR_TOWN_TRAINING ); + IncrementTownLoyalty( ubTownId, uiTownLoyaltyBonus ); } } + + // the trainer announces to player that he's finished his assignment. Make his sector flash! + AssignmentDone( pTrainer, TRUE, FALSE ); + + // handle completion of town by training group + HandleCompletionOfTownTrainingByGroupWithTrainer( pTrainer, TOWN_MILITIA ); } - - // the trainer announces to player that he's finished his assignment. Make his sector flash! - AssignmentDone( pTrainer, TRUE, FALSE ); - - // handle completion of town by training group - HandleCompletionOfTownTrainingByGroupWithTrainer( pTrainer ); - } @@ -640,49 +665,73 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia2"); pMilitiaTrainerSoldier = pSoldier; + // HEADROCK HAM 3.6: Which kind of militia are we training? + UINT8 ubMilitiaType = 0; + if (pSoldier->bAssignment == TRAIN_TOWN) + ubMilitiaType = TOWN_MILITIA; + else if (pSoldier->bAssignment == TRAIN_MOBILE) + ubMilitiaType = MOBILE_MILITIA; + else + return; + // grab total number of sectors - iNumberOfSectors = GetNumberOfUnpaidTrainableSectors( ); + iNumberOfSectors = GetNumberOfUnpaidTrainableSectors( ubMilitiaType ); Assert( iNumberOfSectors > 0 ); // Kaiden: Roaming Militia Training: // We want to charge more for Roaming // get total cost - if( IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA ) - && (gGameExternalOptions.gfmusttrainroaming) - && (GetWorldDay( ) >= gGameExternalOptions.guiAllowMilitiaGroupsDelay)) + // HEADROCK HAM 3.6: Mobile and Garrison Militia now separate + if (ubMilitiaType == TOWN_MILITIA) // Garrison + { + // If Regulars are maxed, and Elites are allowed, calculate cost to upgrade Regular->Elite + if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, REGULAR_MILITIA ) + && (gGameExternalOptions.gfTrainVeteranMilitia) + && (GetWorldDay( ) >= gGameExternalOptions.guiTrainVeteranMilitiaDelay)) + { + giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iVeteranCostModifier) * iNumberOfSectors; + Assert( giTotalCostOfTraining > 0 ); + gfAreWePromotingRegular = TRUE; + } + // If Greens are maxed, calculate cost to upgrade Green->Regular + else if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, GREEN_MILITIA )) + { + giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iRegularCostModifier) * iNumberOfSectors; + Assert( giTotalCostOfTraining > 0 ); + gfAreWePromotingGreen = TRUE; + } + // Normal training. + else + { + giTotalCostOfTraining = iMilitiaTrainingCost * iNumberOfSectors; + Assert( giTotalCostOfTraining > 0 ); + } + } + + // Mobile + else { giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iMilitiaCostModifier) * iNumberOfSectors; Assert( giTotalCostOfTraining > 0 ); gfAreWeTrainingMobile = TRUE; } - else if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, REGULAR_MILITIA ) - && (gGameExternalOptions.gfTrainVeteranMilitia) - && (GetWorldDay( ) >= gGameExternalOptions.guiTrainVeteranMilitiaDelay)) - { - giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iVeteranCostModifier) * iNumberOfSectors; - Assert( giTotalCostOfTraining > 0 ); - gfAreWePromotingRegular = TRUE; - } - else if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, GREEN_MILITIA )) - { - giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iRegularCostModifier) * iNumberOfSectors; - Assert( giTotalCostOfTraining > 0 ); - gfAreWePromotingGreen = TRUE; - } - else - { - giTotalCostOfTraining = iMilitiaTrainingCost * iNumberOfSectors; - Assert( giTotalCostOfTraining > 0 ); - } - gfYesNoPromptIsForContinue = FALSE; if( LaptopSaveInfo.iCurrentBalance < giTotalCostOfTraining ) { - swprintf( sString, pMilitiaConfirmStrings[ 8 ], giTotalCostOfTraining ); - DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); + // Different message for Mobiles + if (ubMilitiaType == TOWN_MILITIA) + { + swprintf( sString, pMilitiaConfirmStrings[ 8 ], giTotalCostOfTraining ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); + } + else + { + swprintf( sString, pMilitiaConfirmStrings[ 11 ], giTotalCostOfTraining ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, CantTrainMobileMilitiaOkBoxCallback ); + } return; } @@ -691,21 +740,50 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia2"); if( iNumberOfSectors > 1 ) { - swprintf( sString, pMilitiaConfirmStrings[ 7 ], iNumberOfSectors, giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ] ); + if (ubMilitiaType == TOWN_MILITIA) + { + swprintf( sString, pMilitiaConfirmStrings[ 7 ], iNumberOfSectors, giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ] ); + } + else + { + swprintf( sString, pMilitiaConfirmStrings[ 13 ], iNumberOfSectors, giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ] ); + } } else { - swprintf( sString, L"%s%d. %s", pMilitiaConfirmStrings[ 0 ], giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ] ); + if (ubMilitiaType == TOWN_MILITIA) + { + swprintf( sString, L"%s%d. %s", pMilitiaConfirmStrings[ 0 ], giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ] ); + } + else + { + swprintf( sString, L"%s%d. %s", pMilitiaConfirmStrings[ 14 ], giTotalCostOfTraining, pMilitiaConfirmStrings[ 1 ] ); + } } // if we are in mapscreen, make a pop up if( guiCurrentScreen == MAP_SCREEN ) { - DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback ); + // HEADROCK HAM 3.6: Separate callbacks for Mobile and Garrison militia + if (ubMilitiaType == TOWN_MILITIA) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback ); + } + else + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMobileMilitiaTrainingYesNoBoxCallback ); + } } else { - DoMessageBox( MSG_BOX_BASIC_STYLE, sString, GAME_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback, &pCenteringRect ); + if (ubMilitiaType == TOWN_MILITIA) + { + DoMessageBox( MSG_BOX_BASIC_STYLE, sString, GAME_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback, &pCenteringRect ); + } + else + { + DoMessageBox( MSG_BOX_BASIC_STYLE, sString, GAME_SCREEN, MSG_BOX_FLAG_YESNO, PayMobileMilitiaTrainingYesNoBoxCallback, &pCenteringRect ); + } } return; @@ -730,7 +808,7 @@ void HandleInterfaceMessageForContinuingTrainingMilitia( SOLDIERTYPE *pSoldier ) CHAR16 sStringB[ 128 ]; INT8 bTownId; BOOLEAN fIsFull = FALSE; - + INT32 iCounter = 0; INT32 iMinLoyaltyToTrain = gGameExternalOptions.iMinLoyaltyToTrain; INT32 iMilitiaTrainingCost = gGameExternalOptions.iMilitiaTrainingCost; @@ -740,7 +818,26 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia3"); sSectorX = pSoldier->sSectorX; sSectorY = pSoldier->sSectorY; - Assert( SectorInfo[ SECTOR( sSectorX, sSectorY ) ].fMilitiaTrainingPaid == FALSE ); + bTownId = GetTownIdForSector( sSectorX, sSectorY ); + + UINT8 ubMilitiaType = 0; + if (pSoldier->bAssignment == TRAIN_TOWN) + { + ubMilitiaType = TOWN_MILITIA; + } + else if (pSoldier->bAssignment == TRAIN_MOBILE ) + { + ubMilitiaType = MOBILE_MILITIA; + } + + if (ubMilitiaType == TOWN_MILITIA) + { + Assert( SectorInfo[ SECTOR( sSectorX, sSectorY ) ].fMilitiaTrainingPaid == FALSE ); + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + Assert( SectorInfo[ SECTOR( sSectorX, sSectorY ) ].fMobileMilitiaTrainingPaid == FALSE ); + } pMilitiaTrainerSoldier = pSoldier; @@ -751,29 +848,21 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia3"); { // loyalty too low to continue training swprintf( sString, pMilitiaConfirmStrings[ 9 ], pTownNames[ GetTownIdForSector( sSectorX, sSectorY )], iMinLoyaltyToTrain ); - DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); + if (ubMilitiaType == TOWN_MILITIA) + { + DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMobileMilitiaOkBoxCallback ); + } return; } - // Kaiden: Roaming Militia Training: - // Changed the if condition from - // if ( IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier ) ) - // And we're not training mobile militia from a SAM Site - - if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) - if (!gGameExternalOptions.gfmusttrainroaming) - fIsFull = TRUE; - else if (GetWorldDay( ) < gGameExternalOptions.guiAllowMilitiaGroupsDelay) - fIsFull = TRUE; - else if (IsThisSectorASAMSector(sSectorX,sSectorY,0 )) - fIsFull = TRUE; - else - fIsFull = FALSE; - - if (fIsFull) + // Training town militia and town is full of Elites (or Regulars, if Elites are disabled) + if (ubMilitiaType == TOWN_MILITIA && IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA )) { // we're full!!! go home! - bTownId = GetTownIdForSector( sSectorX, sSectorY ); if ( bTownId == BLANK_SECTOR ) { // wilderness SAM site @@ -789,33 +878,143 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia3"); DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); return; } + else if (ubMilitiaType == MOBILE_MILITIA) + { + // Test to see if not enough Town Militia + SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR(pSoldier->sSectorX, pSoldier->sSectorY) ] ); + BOOLEAN fUnfullSectorFound = FALSE; + + if ( CountMilitia( pSectorInfo ) < gGameExternalOptions.iMaxMilitiaPerSector ) + { + fUnfullSectorFound = TRUE; + } + else + { + iCounter = 0; + while( pTownNamesList[ iCounter ] != 0 ) + { + // Are we in this city? + if( pTownNamesList[ iCounter] == bTownId ) + { + INT16 sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + INT16 sCurrentY = GET_Y_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + + pSectorInfo = &( SectorInfo[ SECTOR(sCurrentX, sCurrentY) ] ); + // 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. This sector cannot generate militia anyway. + iCounter++; + continue; + } + else + { + if (CountMilitia(pSectorInfo) < gGameExternalOptions.iMaxMilitiaPerSector ) + { + // Found a controlled city sector that does not yet have a full garrison + fUnfullSectorFound; + } + } + } + iCounter++; + } + } + if (fUnfullSectorFound) + { + // At least one city sector is controlled but not full of garrison militia. Can't train mobiles! + swprintf(sString, New113HAMMessage[9], gGameExternalOptions.iMaxMilitiaPerSector, pTownNames[bTownId]); + DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMobileMilitiaOkBoxCallback ); + return; + } + + ////////////////////////////////////////// + // Capacity check in nearby sectors + UINT16 pMoveDir[4][3]; + UINT8 uiDirNumber = 0; + BOOLEAN fFoundValidSector = FALSE; + + + INT8 bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ); + GenerateDirectionInfosForTraining( pSoldier->sSectorX, pSoldier->sSectorY, &uiDirNumber, pMoveDir ); + // Found at least one suitable place to put Mobiles? + if (uiDirNumber) + { + fFoundValidSector = TRUE; + } + // Try entire city. + else + { + INT32 iCounter = 0; + + // Go through each city in the game + while( pTownNamesList[ iCounter ] != 0 ) + { + // Are we in this city? + if( pTownNamesList[ iCounter] == bTownId ) + { + INT16 sCurrentX = GET_X_FROM_STRATEGIC_INDEX( pTownLocationsList[ iCounter ] ); + INT16 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. This sector cannot generate militia anyway. + iCounter++; + continue; + } + + // Find out if any adjacent sectors have room in them. + GenerateDirectionInfosForTraining( sCurrentX, sCurrentY, &uiDirNumber, pMoveDir ); + + if(uiDirNumber) + { + fFoundValidSector = TRUE; + break; + } + } + iCounter++; + } + } + + // Couldn't find at least one sector to place Mobiles. Report "No room!" + if (!fFoundValidSector) + { + swprintf( sString, New113HAMMessage[ 8 ], pTownNames[ bTownId ], iMinLoyaltyToTrain ); + DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMobileMilitiaOkBoxCallback ); + return; + } + } // Kaiden: Roaming Militia Training: // Charging more to train Roaming Militia // Also Charging more for promotions over Training - if( IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, ELITE_MILITIA ) - && (gGameExternalOptions.gfmusttrainroaming) - &&(GetWorldDay( ) >= gGameExternalOptions.guiAllowMilitiaGroupsDelay)) + if( ubMilitiaType == MOBILE_MILITIA ) { giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iMilitiaCostModifier); gfAreWeTrainingMobile = TRUE; } - else if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, REGULAR_MILITIA ) - && (gGameExternalOptions.gfTrainVeteranMilitia) - && (GetWorldDay( ) >= gGameExternalOptions.guiTrainVeteranMilitiaDelay)) - { - giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iVeteranCostModifier); - gfAreWePromotingRegular = TRUE; - } - else if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, GREEN_MILITIA )) - { - giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iRegularCostModifier); - gfAreWePromotingGreen = TRUE; - } + else { - giTotalCostOfTraining = (iMilitiaTrainingCost); + if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, REGULAR_MILITIA ) + && (gGameExternalOptions.gfTrainVeteranMilitia) + && (GetWorldDay( ) >= gGameExternalOptions.guiTrainVeteranMilitiaDelay)) + { + giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iVeteranCostModifier); + gfAreWePromotingRegular = TRUE; + } + else if (IsMilitiaTrainableFromSoldiersSectorMaxed( pSoldier, GREEN_MILITIA )) + { + giTotalCostOfTraining = (iMilitiaTrainingCost*gGameExternalOptions.iRegularCostModifier); + gfAreWePromotingGreen = TRUE; + } + else + { + giTotalCostOfTraining = (iMilitiaTrainingCost); + } } // can player afford to continue training? @@ -823,18 +1022,39 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia3"); { // can't afford to continue training swprintf( sString, pMilitiaConfirmStrings[ 8 ], giTotalCostOfTraining ); - DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); + if (ubMilitiaType == TOWN_MILITIA) + { + DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMilitiaOkBoxCallback ); + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_OK, CantTrainMobileMilitiaOkBoxCallback ); + } return; } // ok to continue, ask player GetSectorIDString( sSectorX, sSectorY, 0, sStringB, TRUE ); - swprintf( sString, pMilitiaConfirmStrings[ 3 ], sStringB, pMilitiaConfirmStrings[ 4 ], giTotalCostOfTraining ); + if (ubMilitiaType == TOWN_MILITIA) + { + swprintf( sString, pMilitiaConfirmStrings[ 3 ], sStringB, pMilitiaConfirmStrings[ 4 ], giTotalCostOfTraining ); + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + swprintf( sString, pMilitiaConfirmStrings[ 12 ], sStringB, pMilitiaConfirmStrings[ 4 ], giTotalCostOfTraining ); + } // ask player whether he'd like to continue training //DoContinueMilitiaTrainingMessageBox( sSectorX, sSectorY, sString, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback ); - DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback ); + if (ubMilitiaType == TOWN_MILITIA) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMilitiaTrainingYesNoBoxCallback ); + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_YESNO, PayMobileMilitiaTrainingYesNoBoxCallback ); + } } @@ -855,15 +1075,16 @@ void PayMilitiaTrainingYesNoBoxCallback( UINT8 bExitValue ) { if( gfYesNoPromptIsForContinue ) { - ContinueTrainingInThisSector(); + ContinueTrainingInThisSector( TOWN_MILITIA ); } else { - StartTrainingInAllUnpaidTrainableSectors(); + StartTrainingInAllUnpaidTrainableSectors( TOWN_MILITIA ); } #ifdef JA2BETAVERSION // put this BEFORE training gets handled to avoid detecting an error everytime a sector completes training + // HEADROCK HAM 3.6: Checks both Mobiles and Garrisons. VerifyTownTrainingIsPaidFor(); #endif @@ -882,7 +1103,7 @@ void PayMilitiaTrainingYesNoBoxCallback( UINT8 bExitValue ) { StopTimeCompression(); - MilitiaTrainingRejected(); + MilitiaTrainingRejected( TOWN_MILITIA ); } return; @@ -891,28 +1112,35 @@ void PayMilitiaTrainingYesNoBoxCallback( UINT8 bExitValue ) void CantTrainMilitiaOkBoxCallback( UINT8 bExitValue ) { - MilitiaTrainingRejected(); + MilitiaTrainingRejected( TOWN_MILITIA ); return; } +// HEADROCK HAM 3.6: Duplicate of the above, for Mobile Militia purposes. +void CantTrainMobileMilitiaOkBoxCallback( UINT8 bExitValue ) +{ + MilitiaTrainingRejected( MOBILE_MILITIA ); + return; +} // IMPORTANT: This same callback is used both for initial training and for continue training prompt // use 'gfYesNoPromptIsForContinue' flag to tell them apart -void MilitiaTrainingRejected( void ) +void MilitiaTrainingRejected( UINT8 ubMilitiaType ) { if( gfYesNoPromptIsForContinue ) { // take all mercs in that sector off militia training - ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( pMilitiaTrainerSoldier->sSectorX, pMilitiaTrainerSoldier->sSectorY ); + ResetAssignmentOfMercsThatWereTrainingMilitiaInThisSector( pMilitiaTrainerSoldier->sSectorX, pMilitiaTrainerSoldier->sSectorY, ubMilitiaType ); } else { // take all mercs in unpaid sectors EVERYWHERE off militia training - ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( 0 ); + ResetAssignmentsForMercsTrainingUnpaidSectorsInSelectedList( ubMilitiaType ); } #ifdef JA2BETAVERSION // put this BEFORE training gets handled to avoid detecting an error everytime a sector completes training + // HEADROCK HAM 3.6: Checks Mobiles and Garrisons VerifyTownTrainingIsPaidFor(); #endif @@ -1149,7 +1377,7 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia5"); } -void HandleCompletionOfTownTrainingByGroupWithTrainer( SOLDIERTYPE *pTrainer ) +void HandleCompletionOfTownTrainingByGroupWithTrainer( SOLDIERTYPE *pTrainer, UINT8 ubMilitiaType ) { INT16 sSectorX = 0, sSectorY = 0; @@ -1180,10 +1408,22 @@ void HandleCompletionOfTownTrainingByGroupWithTrainer( SOLDIERTYPE *pTrainer ) continue; } - if( ( pSoldier->bAssignment == TRAIN_TOWN ) && ( pSoldier->sSectorX == sSectorX )&&( pSoldier->sSectorY == sSectorY )&&( pSoldier->bSectorZ == bSectorZ ) ) + // HEADROCK HAM 3.6: Separated Mobiles from Garrisons. + if ( ubMilitiaType == TOWN_MILITIA) { - // done assignment - AssignmentDone( pSoldier, FALSE, FALSE ); + if( ( pSoldier->bAssignment == TRAIN_TOWN ) && ( pSoldier->sSectorX == sSectorX )&&( pSoldier->sSectorY == sSectorY )&&( pSoldier->bSectorZ == bSectorZ ) ) + { + // done assignment + AssignmentDone( pSoldier, FALSE, FALSE ); + } + } + else + { + if( ( pSoldier->bAssignment == TRAIN_MOBILE ) && ( pSoldier->sSectorX == sSectorX )&&( pSoldier->sSectorY == sSectorY )&&( pSoldier->bSectorZ == bSectorZ ) ) + { + // done assignment + AssignmentDone( pSoldier, FALSE, FALSE ); + } } } @@ -1278,9 +1518,9 @@ void HandleContinueOfTownTraining( void ) //If the militia view isn't currently active, then turn it on when prompting to continue training. if ( !fShowMilitia ) - { - ToggleShowMilitiaMode(); - } + { + ToggleShowMilitiaMode(); + } } return; @@ -1288,7 +1528,7 @@ void HandleContinueOfTownTraining( void ) -void BuildListOfUnpaidTrainableSectors( void ) +void BuildListOfUnpaidTrainableSectors( UINT8 ubMilitiaType ) { INT32 iCounter = 0, iCounterB = 0; SOLDIERTYPE *pSoldier = NULL; @@ -1308,12 +1548,25 @@ void BuildListOfUnpaidTrainableSectors( void ) { pSoldier = &Menptr[ gCharactersList[ iCounter ].usSolID ]; - if( CanCharacterTrainMilitia( pSoldier ) == TRUE ) + // HEADROCK HAM 3.6: Two different conditions depending on the type of militia being trained. + if( ubMilitiaType == TOWN_MILITIA ) { - if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + if (CanCharacterTrainMilitia( pSoldier ) == TRUE ) { - // check to see if this sector is a town and needs equipment - gsUnpaidStrategicSector[ iCounter ] = CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ); + if (SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE) + { + gsUnpaidStrategicSector[ iCounter ] = CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ); + } + } + } + else + { + if (CanCharacterTrainMobileMilitia( pSoldier ) == TRUE ) + { + if (SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMobileMilitiaTrainingPaid == FALSE) + { + gsUnpaidStrategicSector[ iCounter ] = CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ); + } } } } @@ -1326,12 +1579,24 @@ void BuildListOfUnpaidTrainableSectors( void ) pSoldier = &Menptr[ gusUIFullTargetID ]; iCounter = 0; - if( CanCharacterTrainMilitia( pSoldier ) == TRUE ) + if (ubMilitiaType == TOWN_MILITIA) { - if( SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + if (CanCharacterTrainMilitia( pSoldier ) == TRUE ) { - // check to see if this sector is a town and needs equipment - gsUnpaidStrategicSector[ iCounter ] = CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ); + if (SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMilitiaTrainingPaid == FALSE ) + { + gsUnpaidStrategicSector[ iCounter ] = CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ); + } + } + } + else + { + if (CanCharacterTrainMobileMilitia( pSoldier ) == TRUE ) + { + if (SectorInfo[ SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) ].fMobileMilitiaTrainingPaid == FALSE ) + { + gsUnpaidStrategicSector[ iCounter ] = CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY ); + } } } } @@ -1354,12 +1619,12 @@ void BuildListOfUnpaidTrainableSectors( void ) -INT32 GetNumberOfUnpaidTrainableSectors( void ) +INT32 GetNumberOfUnpaidTrainableSectors( UINT8 ubMilitiaType ) { INT32 iCounter = 0; INT32 iNumberOfSectors = 0; - BuildListOfUnpaidTrainableSectors(); + BuildListOfUnpaidTrainableSectors( ubMilitiaType ); // now count up the results for( iCounter = 0; iCounter < giMAXIMUM_NUMBER_OF_PLAYER_SLOTS; iCounter++ ) @@ -1376,15 +1641,21 @@ INT32 GetNumberOfUnpaidTrainableSectors( void ) } -void StartTrainingInAllUnpaidTrainableSectors() +void StartTrainingInAllUnpaidTrainableSectors( UINT8 ubMilitiaType ) { INT32 iCounter = 0; UINT8 ubSector; + if (ubMilitiaType == TOWN_MILITIA) + { + SetAssignmentForList( TRAIN_TOWN, 0 ); + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + SetAssignmentForList( TRAIN_MOBILE, 0 ); + } - SetAssignmentForList( TRAIN_TOWN, 0 ); - - BuildListOfUnpaidTrainableSectors(); + BuildListOfUnpaidTrainableSectors( ubMilitiaType ); // pay up in each sector for( iCounter = 0; iCounter < giMAXIMUM_NUMBER_OF_PLAYER_SLOTS; iCounter++ ) @@ -1399,7 +1670,7 @@ void StartTrainingInAllUnpaidTrainableSectors() } -void ContinueTrainingInThisSector( void ) +void ContinueTrainingInThisSector( UINT8 ubMilitiaType ) { UINT8 ubSector; @@ -1415,8 +1686,9 @@ void PayForTrainingInSector( UINT8 ubSector ) { INT32 iMilitiaTrainingCost = gGameExternalOptions.iMilitiaTrainingCost; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia6"); - Assert( SectorInfo[ ubSector ].fMilitiaTrainingPaid == FALSE ); + INT32 CostMultiplyer = 0; + UINT8 ubMilitiaType = 0; // spend the money // Kaiden: Roaming Militia Training: @@ -1425,35 +1697,43 @@ DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"Militia6"); if ( gfAreWeTrainingMobile) { + Assert( SectorInfo[ ubSector ].fMobileMilitiaTrainingPaid == FALSE ); + // Mark sector as being paid up + SectorInfo[ ubSector ].fMobileMilitiaTrainingPaid = TRUE; CostMultiplyer = gGameExternalOptions.iMilitiaCostModifier; gfAreWeTrainingMobile = FALSE; + ubMilitiaType = MOBILE_MILITIA; } - else if ( gfAreWePromotingGreen) + else { - CostMultiplyer = gGameExternalOptions.iRegularCostModifier; - gfAreWePromotingGreen = FALSE; - } - else if ( gfAreWePromotingRegular) - { - CostMultiplyer = gGameExternalOptions.iVeteranCostModifier; - gfAreWePromotingRegular = FALSE; - } - else - { - CostMultiplyer = 1; + Assert( SectorInfo[ ubSector ].fMilitiaTrainingPaid == FALSE ); + if ( gfAreWePromotingGreen) + { + CostMultiplyer = gGameExternalOptions.iRegularCostModifier; + gfAreWePromotingGreen = FALSE; + } + else if ( gfAreWePromotingRegular) + { + CostMultiplyer = gGameExternalOptions.iVeteranCostModifier; + gfAreWePromotingRegular = FALSE; + } + else + { + CostMultiplyer = 1; + } + // Mark sector as having paid + SectorInfo[ ubSector ].fMilitiaTrainingPaid = TRUE; + ubMilitiaType = TOWN_MILITIA; } AddTransactionToPlayersBook( TRAIN_TOWN_MILITIA, ubSector, GetWorldTotalMin(), -( iMilitiaTrainingCost*CostMultiplyer ) ); - // mark this sector sectors as being paid up - SectorInfo[ ubSector ].fMilitiaTrainingPaid = TRUE; - // reset done flags - ResetDoneFlagForAllMilitiaTrainersInSector( ubSector ); + ResetDoneFlagForAllMilitiaTrainersInSector( ubSector, ubMilitiaType ); } -void ResetDoneFlagForAllMilitiaTrainersInSector( UINT8 ubSector ) +void ResetDoneFlagForAllMilitiaTrainersInSector( UINT8 ubSector, UINT8 ubMilitiaType ) { INT32 iCounter = 0; SOLDIERTYPE *pSoldier = NULL; @@ -1465,12 +1745,26 @@ void ResetDoneFlagForAllMilitiaTrainersInSector( UINT8 ubSector ) if( pSoldier->bActive ) { - if( pSoldier->bAssignment == TRAIN_TOWN ) + if (ubMilitiaType == TOWN_MILITIA) { - if( ( SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) == ubSector ) && ( pSoldier->bSectorZ == 0 ) ) + if( pSoldier->bAssignment == TRAIN_TOWN ) { - pSoldier->flags.fDoneAssignmentAndNothingToDoFlag = FALSE; - pSoldier->usQuoteSaidExtFlags &= ~SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT; + if( ( SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) == ubSector ) && ( pSoldier->bSectorZ == 0 ) ) + { + pSoldier->flags.fDoneAssignmentAndNothingToDoFlag = FALSE; + pSoldier->usQuoteSaidExtFlags &= ~SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT; + } + } + } + else if (ubMilitiaType == MOBILE_MILITIA) + { + if( pSoldier->bAssignment == TRAIN_MOBILE ) + { + if( ( SECTOR( pSoldier->sSectorX, pSoldier->sSectorY ) == ubSector ) && ( pSoldier->bSectorZ == 0 ) ) + { + pSoldier->flags.fDoneAssignmentAndNothingToDoFlag = FALSE; + pSoldier->usQuoteSaidExtFlags &= ~SOLDIER_QUOTE_SAID_DONE_ASSIGNMENT; + } } } } @@ -1578,3 +1872,419 @@ void BuildMilitiaPromotionsString( STR16 str ) gbRegToElitePromotions = 0; gbMilitiaPromotions = 0; } + + +// HEADROCK HAM 3.3: This function finds the best Militia Trainer in the target sector. It may also take into account +// mercs' TEACHING skill, if told to. The function returns the calculated "Effective Leadership" of the selected +// trainer. +UINT8 FindBestMilitiaTrainingLeadershipInSector ( INT16 sMapX, INT16 sMapY, INT8 bMapZ, UINT8 ubMilitiaType ) +{ + + UINT16 cnt = 0; + SOLDIERTYPE * pCheckedTrainer; + UINT16 usTrainerEffectiveLeadership = 0; + UINT8 ubBestLeadership = 0; + + // HEADROCK HAM 3.3: Add search for best trainer in sector + for ( pCheckedTrainer = MercPtrs[ cnt ]; cnt <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; cnt++, pCheckedTrainer++) + { + if (pCheckedTrainer->bActive && pCheckedTrainer->stats.bLife >= OKLIFE && + ((ubMilitiaType == TOWN_MILITIA && pCheckedTrainer->bAssignment == TRAIN_TOWN) || + (ubMilitiaType == MOBILE_MILITIA && pCheckedTrainer->bAssignment == TRAIN_MOBILE) ) ) + { + if (pCheckedTrainer->sSectorX == sMapX && pCheckedTrainer->sSectorY == sMapY && pCheckedTrainer->bSectorZ == bMapZ ) + { + usTrainerEffectiveLeadership = EffectiveLeadership(pCheckedTrainer); + + // Effective leadership is modified by an INI-based percentage, once for every TEACHING trait level. + if ( gGameExternalOptions.usTeacherTraitEffectOnLeadership > 0 && gGameExternalOptions.usTeacherTraitEffectOnLeadership != 100 ) + { + for (UINT8 i = 0; i < NUM_SKILL_TRAITS( pCheckedTrainer, TEACHING ); i++ ) + { + // percentage-based. + usTrainerEffectiveLeadership = __min(100,((usTrainerEffectiveLeadership * gGameExternalOptions.usTeacherTraitEffectOnLeadership)/100)); + } + } + + if (usTrainerEffectiveLeadership > ubBestLeadership) + { + ubBestLeadership = __min(100,usTrainerEffectiveLeadership); + } + } + } + } + + return(ubBestLeadership); +} + +// HEADROCK HAM 3.6: Created a new function to make things easier. This calculates the target number of militia to +// be created. +UINT8 CalcNumMilitiaTrained(UINT8 ubBestLeadership, BOOLEAN fMobile) +{ + + UINT8 ubReqLeadershipForFull = 1; + UINT8 ubMinLeadershipForTraining = 0; + UINT8 ubLeadershipRange = 0; + + // Result value + UINT8 ubMilitiaToTrain = 0; + + + // Garrison Training + if (!fMobile) + { + // Default Squad Size for Garrisons, read from INI. + ubMilitiaToTrain = gGameExternalOptions.iTrainingSquadSize; + + // Is affected by Leadership? + if (gGameExternalOptions.fLeadershipAffectsMilitiaQuantity && + gGameExternalOptions.ubReqLeadershipForFullTraining > gGameExternalOptions.ubMinimumLeadershipToTrainMilitia) + { + // Set these variables to their INI values. + ubReqLeadershipForFull = gGameExternalOptions.ubReqLeadershipForFullTraining; + ubMinLeadershipForTraining = gGameExternalOptions.ubMinimumLeadershipToTrainMilitia; + + // We make sure our effective leadership isn't above or below the limits. If so, reset to limits. + // If there is a minimum required to train militia, we're ASSUMING that the character has enough leadership + // otherwise they wouldn't even be here. But just in case he's skipped the check somehow, this will raise + // leadership to the minimum to avoid problems. + ubBestLeadership = __max(ubBestLeadership, ubMinLeadershipForTraining); + ubBestLeadership = __min(ubBestLeadership, ubReqLeadershipForFull); + + // Find out by how much we've beaten the skill check + ubBestLeadership = ubBestLeadership - ubMinLeadershipForTraining; + + // Find the range between the minimum and maximum. + ubLeadershipRange = ubReqLeadershipForFull - ubMinLeadershipForTraining; + + // Squad Size is determined by the relative value of the leadership within the range. Anyone meeting or + // exceeding the range (latter shouldn't happen) will train a full squad. Others will train less. If + // leadership was the minimum required, only 1 militia is trained. + ubMilitiaToTrain = ((ubBestLeadership * ubMilitiaToTrain) / ubLeadershipRange) + 1; + ubMilitiaToTrain = __min(ubMilitiaToTrain, gGameExternalOptions.iTrainingSquadSize); + ubMilitiaToTrain = __max(1, ubMilitiaToTrain); + } + } + + // Mobile Militia + else + { + // Default Squad Size for Mobiles, read from INI. + ubMilitiaToTrain = (gGameExternalOptions.iMaxMilitiaPerSector / gGameExternalOptions.guiDivOfOriginalMilitia); + + // Is affected by Leadership? + if (gGameExternalOptions.fLeadershipAffectsMobileMilitiaQuantity && + gGameExternalOptions.ubReqLeadershipForFullMobileTraining > gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia ) + { + + // Set these variables to their INI values. + ubReqLeadershipForFull = gGameExternalOptions.ubReqLeadershipForFullMobileTraining; + ubMinLeadershipForTraining = gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia; + + // We make sure our effective leadership isn't above or below the limits. If so, reset to limits. + // If there is a minimum required to train militia, we're ASSUMING that the character has enough leadership + // otherwise they wouldn't even be here. But just in case he's skipped the check somehow, this will raise + // leadership to the minimum to avoid problems. + ubBestLeadership = __max(ubBestLeadership, ubMinLeadershipForTraining); + ubBestLeadership = __min(ubBestLeadership, ubReqLeadershipForFull); + + // Find out by how much we've beaten the skill check + ubBestLeadership = ubBestLeadership - ubMinLeadershipForTraining; + + // Find the range between the minimum and maximum. + ubLeadershipRange = ubReqLeadershipForFull - ubMinLeadershipForTraining; + + // Squad Size is determined by the relative value of the leadership within the range. Anyone meeting or + // exceeding the range (latter shouldn't happen) will train a full squad. Others will train less. If + // leadership was the minimum required, only 3 militia are trained. + ubMilitiaToTrain = ((ubBestLeadership * ubMilitiaToTrain) / ubLeadershipRange) + 1; + ubMilitiaToTrain = __min(ubMilitiaToTrain, gGameExternalOptions.iMaxMilitiaPerSector / gGameExternalOptions.guiDivOfOriginalMilitia); + ubMilitiaToTrain = __max(3, ubMilitiaToTrain); + } + } + + return (ubMilitiaToTrain); +} + +// HEADROCK HAM 3.6: Duplicate function for handling callback on Mobile Militia training +void PayMobileMilitiaTrainingYesNoBoxCallback( UINT8 bExitValue ) +{ + CHAR16 sString[ 128 ]; + + Assert( giTotalCostOfTraining > 0 ); + + // yes + if( bExitValue == MSG_BOX_RETURN_YES ) + { + // does the player have enough + if( LaptopSaveInfo.iCurrentBalance >= giTotalCostOfTraining ) + { + if( gfYesNoPromptIsForContinue ) + { + ContinueTrainingInThisSector( MOBILE_MILITIA ); + } + else + { + StartTrainingInAllUnpaidTrainableSectors( MOBILE_MILITIA ); + } + +#ifdef JA2BETAVERSION + // put this BEFORE training gets handled to avoid detecting an error everytime a sector completes training + // Checks Town and Mobile militia + VerifyTownTrainingIsPaidFor(); +#endif + + // this completes the training prompt sequence + pMilitiaTrainerSoldier = NULL; + } + else // can't afford it + { + StopTimeCompression(); + + swprintf( sString, L"%s", pMilitiaConfirmStrings[ 2 ] ); + DoMapMessageBox( MSG_BOX_BASIC_STYLE, sString, MAP_SCREEN, MSG_BOX_FLAG_OK, CantTrainMobileMilitiaOkBoxCallback ); + } + } + else if( bExitValue == MSG_BOX_RETURN_NO ) + { + StopTimeCompression(); + + MilitiaTrainingRejected( MOBILE_MILITIA ); + } + + return; +} + +// HEADROCK HAM 3.6: Daily check for upkeep of all militia +void HandleMilitiaUpkeepPayment( void ) +{ + UINT32 uiNewTotalUpkeep = CalcMilitiaUpkeep(); + + if (guiTotalUpkeepForMilitia > 0) + { + // Yesterday night we already had some militia, so we're eligible to pay tonight. + + // Pay no more than what we owed yesterday. Militia added today don't get paid. + UINT32 uiPayment = __min( uiNewTotalUpkeep, guiTotalUpkeepForMilitia ); + + if (uiPayment > 0) + { + // Do we have enough money? + if (LaptopSaveInfo.iCurrentBalance < (INT32)uiPayment) + { + INT32 uiMoneyUnpaid = uiPayment - LaptopSaveInfo.iCurrentBalance; + + // Find and remove militia across the map. This is done randomly, starting with elites. + UINT32 uiNumMilitiaDisbanded = 0; + + MILITIA_LIST_TYPE MilitiaList[256]; + + // Go through every sector and count how many militia we've got in total. + for (UINT16 cnt = 0; cnt < 256; cnt++) + { + SECTORINFO *pSectorInfo = &( SectorInfo[ cnt ] ); + UINT8 sMapX = SECTORX(cnt); + UINT8 sMapY = SECTORY(cnt); + + MilitiaList[cnt].ubSectorId = (UINT8)cnt; + + UINT8 ubTownId = GetTownIdForSector( sMapX, sMapY ); + if ((ubTownId != BLANK_SECTOR && MilitiaTrainingAllowedInTown(ubTownId)) || // Major Town + IsThisSectorASAMSector( sMapX, sMapY, 0 ) ) // SAM Site + { + MilitiaList[cnt].ubNumTownGreens = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]; + MilitiaList[cnt].ubNumTownRegulars = pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]; + MilitiaList[cnt].ubNumTownElites = pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]; + MilitiaList[cnt].ubNumMobileGreens = 0; + MilitiaList[cnt].ubNumMobileRegulars = 0; + MilitiaList[cnt].ubNumMobileElites = 0; + } + else + { + MilitiaList[cnt].ubNumMobileGreens = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]; + MilitiaList[cnt].ubNumMobileRegulars = pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]; + MilitiaList[cnt].ubNumMobileElites = pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]; + MilitiaList[cnt].ubNumTownGreens = 0; + MilitiaList[cnt].ubNumTownRegulars = 0; + MilitiaList[cnt].ubNumTownElites = 0; + } + } + + // Resort list so that sectors with more militia are at the top of the array. Higher weight + // for mobiles, and moreso for elites, then regulars, then greens. + qsort( MilitiaList, 256, sizeof(MILITIA_LIST_TYPE), MilitiaListQsortCompare); + + while (uiMoneyUnpaid > 0) + { + if (MilitiaList[0].ubNumTownGreens + + MilitiaList[0].ubNumTownRegulars + + MilitiaList[0].ubNumTownElites + + MilitiaList[0].ubNumMobileGreens + + MilitiaList[0].ubNumMobileRegulars + + MilitiaList[0].ubNumMobileElites == 0) + { + //All sectors are empty? Default to 0 payment. + uiMoneyUnpaid = 0; + } + + SECTORINFO *pSectorInfo = &( SectorInfo[ MilitiaList[0].ubSectorId ] ); + + if (MilitiaList[0].ubNumMobileElites > 0) + { + // Remove one militia from sector + pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]--; + // Adjust list entry + MilitiaList[0].ubNumMobileElites--; + // Reduce debt appropriately + uiMoneyUnpaid -= gGameExternalOptions.usDailyCostMobileElite; + // Increase tally of militia removed + uiNumMilitiaDisbanded++; + } + else if (MilitiaList[0].ubNumMobileRegulars > 0) + { + pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]--; + MilitiaList[0].ubNumMobileRegulars--; + uiMoneyUnpaid -= gGameExternalOptions.usDailyCostMobileRegular; + uiNumMilitiaDisbanded++; + } + else if (MilitiaList[0].ubNumMobileGreens > 0) + { + pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]--; + MilitiaList[0].ubNumMobileGreens--; + uiMoneyUnpaid -= gGameExternalOptions.usDailyCostMobileGreen; + uiNumMilitiaDisbanded++; + } + else if (MilitiaList[0].ubNumTownElites > 0) + { + pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA]--; + MilitiaList[0].ubNumTownElites--; + uiMoneyUnpaid -= gGameExternalOptions.usDailyCostTownElite; + uiNumMilitiaDisbanded++; + } + else if (MilitiaList[0].ubNumTownRegulars > 0) + { + pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]--; + MilitiaList[0].ubNumTownRegulars--; + uiMoneyUnpaid -= gGameExternalOptions.usDailyCostTownRegular; + uiNumMilitiaDisbanded++; + } + else if (MilitiaList[0].ubNumTownGreens > 0) + { + pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]--; + MilitiaList[0].ubNumTownGreens--; + uiMoneyUnpaid -= gGameExternalOptions.usDailyCostTownGreen; + uiNumMilitiaDisbanded++; + } + else + { + // Somehow there are none to remove?! + Assert(1); + } + // Resort the list! + qsort( MilitiaList, 256, sizeof(MILITIA_LIST_TYPE), MilitiaListQsortCompare); + } + + // Pay all we can + uiPayment = LaptopSaveInfo.iCurrentBalance; + + // Notify player that X militia have been automatically disbanded due to debt. + CHAR16 sString[256]; + swprintf( sString, New113HAMMessage[21], uiNumMilitiaDisbanded ); + DoScreenIndependantMessageBox( sString, MSG_BOX_FLAG_OK, NULL ); + } + AddTransactionToPlayersBook( MILITIA_UPKEEP, 0, GetWorldTotalMin(), (-1) * (INT32)uiPayment ); + } + } + + // Record tonight's upkeep costs + guiTotalUpkeepForMilitia = uiNewTotalUpkeep; +} + +int MilitiaListQsortCompare(const void *pArg1, const void *pArg2) +{ + if (((MILITIA_LIST_TYPE *)pArg1)->ubNumMobileElites > ((MILITIA_LIST_TYPE *)pArg2)->ubNumMobileElites) + { + return(-1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumMobileRegulars > ((MILITIA_LIST_TYPE *)pArg2)->ubNumMobileRegulars) + { + return(-1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumMobileGreens > ((MILITIA_LIST_TYPE *)pArg2)->ubNumMobileGreens) + { + return(-1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumTownElites > ((MILITIA_LIST_TYPE *)pArg2)->ubNumTownElites) + { + return(-1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumTownRegulars > ((MILITIA_LIST_TYPE *)pArg2)->ubNumTownRegulars) + { + return(-1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumTownGreens > ((MILITIA_LIST_TYPE *)pArg2)->ubNumTownGreens) + { + return(-1); + } + else + { + if (((MILITIA_LIST_TYPE *)pArg1)->ubNumTownGreens < ((MILITIA_LIST_TYPE *)pArg2)->ubNumTownGreens) + { + return(1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumTownRegulars < ((MILITIA_LIST_TYPE *)pArg2)->ubNumTownRegulars) + { + return(1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumTownElites < ((MILITIA_LIST_TYPE *)pArg2)->ubNumTownElites) + { + return(1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumMobileGreens < ((MILITIA_LIST_TYPE *)pArg2)->ubNumMobileGreens) + { + return(1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumMobileRegulars < ((MILITIA_LIST_TYPE *)pArg2)->ubNumMobileRegulars) + { + return(1); + } + else if (((MILITIA_LIST_TYPE *)pArg1)->ubNumMobileElites < ((MILITIA_LIST_TYPE *)pArg2)->ubNumMobileElites) + { + return(1); + } + else + { + return(0); + } + } +} + +// Calculate amount to pay for militia upkeep today. +UINT32 CalcMilitiaUpkeep( void ) +{ + UINT32 uiTotalPayment = 0; + + for (UINT16 cnt = 0; cnt < 256; cnt++) + { + SECTORINFO *pSectorInfo = &( SectorInfo[ cnt ] ); + UINT8 sMapX = SECTORX(cnt); + UINT8 sMapY = SECTORY(cnt); + + UINT8 ubTownId = GetTownIdForSector( sMapX, sMapY ); + if ((ubTownId != BLANK_SECTOR && MilitiaTrainingAllowedInTown(ubTownId)) || // Major Town + IsThisSectorASAMSector( sMapX, sMapY, 0 ) ) // SAM Site + { + uiTotalPayment += pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA] * gGameExternalOptions.usDailyCostTownGreen; + uiTotalPayment += pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA] * gGameExternalOptions.usDailyCostTownRegular; + uiTotalPayment += pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] * gGameExternalOptions.usDailyCostTownElite; + } + else + { + uiTotalPayment += pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA] * gGameExternalOptions.usDailyCostMobileGreen; + uiTotalPayment += pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA] * gGameExternalOptions.usDailyCostMobileRegular; + uiTotalPayment += pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] * gGameExternalOptions.usDailyCostMobileElite; + } + } + + return (uiTotalPayment); +} diff --git a/Strategic/Town Militia.h b/Strategic/Town Militia.h index d8d58b14..ac0d1c92 100644 --- a/Strategic/Town Militia.h +++ b/Strategic/Town Militia.h @@ -39,6 +39,11 @@ UINT8 CheckOneMilitiaForPromotion(INT16 sMapX, INT16 sMapY, UINT8 ubCurrentRank, void BuildMilitiaPromotionsString( STR16 str ); +// HEADROCK HAM 3.3: Function to determine best leadership for militia training in a given sector. +// Is used for both town and mobile militia. +// HEADROCK HAM 3.6: Argument for Mobile/Town Militia training +UINT8 FindBestMilitiaTrainingLeadershipInSector ( INT16 sMapX, INT16 sMapY, INT8 usMapZ, UINT8 ubMilitiaType ); + // call this if the player attacks his own militia void HandleMilitiaDefections(INT16 sMapX, INT16 sMapY); @@ -70,7 +75,8 @@ BOOLEAN IsSAMSiteFullOfMilitia( INT16 sSectorX, INT16 sSectorY, INT8 iMilitiaTyp void HandleContinueOfTownTraining( void ); // handle completion of assignment byt his soldier too and inform the player -void HandleCompletionOfTownTrainingByGroupWithTrainer( SOLDIERTYPE *pTrainer ); +// HEADROCK HAM 3.6: Added argument for Mobile Militia training. +void HandleCompletionOfTownTrainingByGroupWithTrainer( SOLDIERTYPE *pTrainer, UINT8 ubMilitiaType ); // clear the list of training completed sectors void ClearSectorListForCompletedTrainingOfMilitia( void ); @@ -78,4 +84,29 @@ void ClearSectorListForCompletedTrainingOfMilitia( void ); BOOLEAN MilitiaTrainingAllowedInSector( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ ); BOOLEAN MilitiaTrainingAllowedInTown( INT8 bTownId ); +// HEADROCK HAM 3.6: Function to calculate Militia and Mobile Militia training squad size. +UINT8 CalcNumMilitiaTrained(UINT8 ubBestLeadership, BOOLEAN fMobile); + +// HEADROCK HAM 3.6: Total upkeep costs for YESTERDAY at midnight. Saved to savegames. +extern UINT32 guiTotalUpkeepForMilitia; + +// HEADROCK HAM 3.6: Daily check for upkeep of all militia +void HandleMilitiaUpkeepPayment( void ); + +// Type to hold number of militia in each sector. +typedef struct MILITIA_LIST_TYPE +{ + UINT8 ubSectorId; + UINT8 ubNumTownGreens; + UINT8 ubNumTownRegulars; + UINT8 ubNumTownElites; + UINT8 ubNumMobileGreens; + UINT8 ubNumMobileRegulars; + UINT8 ubNumMobileElites; + +} MILITIA_LIST_TYPE; + +// HEADROCK HAM 3.6: Calculate upkeep costs for militia +UINT32 CalcMilitiaUpkeep( void ); + #endif \ No newline at end of file diff --git a/Strategic/XML_Bloodcats.cpp b/Strategic/XML_Bloodcats.cpp new file mode 100644 index 00000000..3347ae00 --- /dev/null +++ b/Strategic/XML_Bloodcats.cpp @@ -0,0 +1,343 @@ +#ifdef PRECOMPILEDHEADERS + #include "Strategic All.h" + #include "XML.h" +#else + #include "builddefines.h" + #include + #include "XML.h" + #include "expat.h" + #include "string.h" + #include "Campaign Types.h" + #include "FileMan.h" + #include "MemMan.h" + #include "Debug Control.h" + #include "mapscreen.h" +#endif + +#define MAX_CHAR_DATA_LENGTH 500 + +// This array stores all XML-read placement data. There are four arrays for each sector, one array for each difficulty +// level. Please note that the Placement Type is only stored in array 0 (gBloodcatPlacements[x][0].PlacementType). It +// is not possible to have different placement types at different difficulty levels. Yet. +BLOODCATPLACEMENT gBloodcatPlacements[256][4]; +// Location of the Bloodcat Lair. There can be only one, as there's a quest that relies on its location. This is a +// sectorID, 0-255. +UINT8 gubBloodcatLairSectorId; + +typedef enum +{ + BLOODCAT_ELEMENT_NONE = 0, + BLOODCAT_ELEMENT_PLACEMENTS, + BLOODCAT_ELEMENT_SECTOR, + BLOODCAT_ELEMENT, + BLOODCAT_ELEMENT_DIFFICULTY, + BLOODCAT_ELEMENT_DIFFICULTY_DATA, +} BLOODCAT_PARSE_STAGE; + +typedef struct +{ + BLOODCAT_PARSE_STAGE curElement; + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + UINT8 sCurSectorX; + UINT8 sCurSectorY; + BLOODCATPLACEMENT CurPlacement[4]; + UINT8 ubCurDifficulty; + UINT32 currentDepth; + UINT32 maxReadDepth; +} BloodcatsParseData; + + +static void XMLCALL +BloodcatsStartElementHandle(void *userData, const XML_Char *name, const char **atts) +{ + BloodcatsParseData * pData = (BloodcatsParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + + if(strcmp(name, "BLOODCAT_PLACEMENTS") == 0 && pData->curElement == BLOODCAT_ELEMENT_NONE) + { + pData->curElement = BLOODCAT_ELEMENT_PLACEMENTS; + pData->maxReadDepth++; //we are not skipping this element + // Initiate Array by setting first character to 0. + for (UINT16 x = 0; x < 256; x++) + { + for (UINT16 y = 0; y < 4; y++) + { + gBloodcatPlacements[x][y].ubFactionAffiliation = 0; + gBloodcatPlacements[x][y].fRespawn = 0; + gBloodcatPlacements[x][y].PlacementType = 0; + gBloodcatPlacements[x][y].ubAmbushChance = 0; + gBloodcatPlacements[x][y].ubMaxBloodcats = 0; + gBloodcatPlacements[x][y].ubMinBloodcats = 0; + } + } + } + + else if(strcmp(name, "SECTOR") == 0 && pData->curElement == BLOODCAT_ELEMENT_PLACEMENTS) + { + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + pData->maxReadDepth++; //we are not skipping this element + } + + else if(pData->curElement == BLOODCAT_ELEMENT_SECTOR && + (strcmp(name, "SectorGrid") == 0 || + strcmp(name, "ubPlacementType") == 0) ) + { + pData->curElement = BLOODCAT_ELEMENT; + + pData->maxReadDepth++; //we are not skipping this element + } + + else if(pData->curElement == BLOODCAT_ELEMENT_SECTOR && + strcmp(name, "NOVICE") == 0) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->ubCurDifficulty = 0; + pData->maxReadDepth++; //we are not skipping this element + } + + else if(pData->curElement == BLOODCAT_ELEMENT_SECTOR && + strcmp(name, "EXPERIENCED") == 0) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->ubCurDifficulty = 1; + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == BLOODCAT_ELEMENT_SECTOR && + strcmp(name, "EXPERT") == 0) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->ubCurDifficulty = 2; + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == BLOODCAT_ELEMENT_SECTOR && + strcmp(name, "INSANE") == 0) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->ubCurDifficulty = 3; + pData->maxReadDepth++; //we are not skipping this element + } + + else if(pData->curElement == BLOODCAT_ELEMENT_DIFFICULTY && + (strcmp(name, "ubMinBloodcats") == 0 || + strcmp(name, "ubMaxBloodcats") == 0 || + strcmp(name, "ubAmbushChance") == 0 || + strcmp(name, "ubFactionAffiliation") == 0 || + strcmp(name, "fRespawn") == 0 )) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY_DATA; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + + + +static void XMLCALL +BloodcatsCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + BloodcatsParseData * pData = (BloodcatsParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth && strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); +} + + +static void XMLCALL +BloodcatsEndElementHandle(void *userData, const XML_Char *name) +{ + BloodcatsParseData * pData = (BloodcatsParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "BLOODCAT_PLACEMENTS") == 0) + { + pData->curElement = BLOODCAT_ELEMENT_NONE; + } + else if(strcmp(name, "SECTOR") == 0) + { + pData->curElement = BLOODCAT_ELEMENT_PLACEMENTS; + + UINT8 ubSectorId = SECTOR(pData->sCurSectorX, pData->sCurSectorY); + if (ubSectorId >= 0 && ubSectorId < 256) + { + // Set placement type for this sector. Only first array has this set, as it is not difficulty-dependent. + gBloodcatPlacements[ ubSectorId ][0].PlacementType = pData->CurPlacement[0].PlacementType; + for (UINT8 x = 0; x < 4; x++) + { + if (pData->CurPlacement[0].PlacementType == 0) + { + // Ambush placement. Bloodcats will appear in this sector randomly. + gBloodcatPlacements[ ubSectorId ][x].ubMinBloodcats = pData->CurPlacement[x].ubMinBloodcats; + gBloodcatPlacements[ ubSectorId ][x].ubMaxBloodcats = pData->CurPlacement[x].ubMaxBloodcats; + gBloodcatPlacements[ ubSectorId ][x].ubAmbushChance = pData->CurPlacement[x].ubAmbushChance; + gBloodcatPlacements[ ubSectorId ][x].ubFactionAffiliation = 0; + gBloodcatPlacements[ ubSectorId ][x].fRespawn = 0; + } + + else if (pData->CurPlacement[0].PlacementType == 1) + { + // Static placement. Bloodcats always appear here, but never respawn, and may be unaggressive + // to enemies. + gBloodcatPlacements[ ubSectorId ][x].ubMinBloodcats = 0; + gBloodcatPlacements[ ubSectorId ][x].ubMaxBloodcats = pData->CurPlacement[x].ubMaxBloodcats; + gBloodcatPlacements[ ubSectorId ][x].ubAmbushChance = 0; + gBloodcatPlacements[ ubSectorId ][x].ubFactionAffiliation = pData->CurPlacement[x].ubFactionAffiliation; + gBloodcatPlacements[ ubSectorId ][x].fRespawn = 0; + } + + else if (pData->CurPlacement[0].PlacementType == 2) + { + // Lair sector. There can be only one, and it is affected by the Quest. Bloodcats can regenerate + // their numbers here over time. + gBloodcatPlacements[ ubSectorId ][x].ubMinBloodcats = 0; + gBloodcatPlacements[ ubSectorId ][x].ubMaxBloodcats = pData->CurPlacement[x].ubMaxBloodcats; + gBloodcatPlacements[ ubSectorId ][x].ubAmbushChance = 0; + gBloodcatPlacements[ ubSectorId ][x].ubFactionAffiliation = 0; + gBloodcatPlacements[ ubSectorId ][x].fRespawn = pData->CurPlacement[x].fRespawn; + } + } + } + } + + else if(strcmp(name, "SectorGrid") == 0 ) + { + UINT8 x, y; + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + + y = (UINT8)pData->szCharData[0] & 0x1F; + x = (UINT8)atol(&pData->szCharData[1]); + if ( x > 0 && x <= 16 && y > 0 && y <= 16 ) + { + pData->sCurSectorX = x; + pData->sCurSectorY = y; + } + THROWIFFALSE( (SECTOR(x,y) >= 0 && SECTOR(x,y) < 256) , L"Illegal sector number in BloodcatPlacements.XML" ); + } + else if(strcmp(name, "ubPlacementType") == 0 ) + { + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + // Only set for first array (Difficulty Irrelevant) + pData->CurPlacement[0].PlacementType = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "NOVICE") == 0 ) + { + // Just return to sector data. + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + } + + else if(strcmp(name, "EXPERIENCED") == 0 ) + { + // Just return to sector data. + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + } + + else if(strcmp(name, "EXPERT") == 0 ) + { + // Just return to sector data. + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + } + + else if(strcmp(name, "INSANE") == 0 ) + { + // Just return to sector data. + pData->curElement = BLOODCAT_ELEMENT_SECTOR; + } + + else if(strcmp(name, "ubMinBloodcats") == 0 ) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->CurPlacement[pData->ubCurDifficulty].ubMinBloodcats = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMaxBloodcats") == 0 ) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->CurPlacement[pData->ubCurDifficulty].ubMaxBloodcats = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubAmbushChance") == 0 ) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->CurPlacement[pData->ubCurDifficulty].ubAmbushChance = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubFactionAffiliation") == 0 ) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->CurPlacement[pData->ubCurDifficulty].ubFactionAffiliation = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "fRespawn") == 0 ) + { + pData->curElement = BLOODCAT_ELEMENT_DIFFICULTY; + pData->CurPlacement[pData->ubCurDifficulty].fRespawn = (UINT8) atol(pData->szCharData); + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + +BOOLEAN ReadInBloodcatPlacements(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + BloodcatsParseData pData; + + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, BloodcatsStartElementHandle, BloodcatsEndElementHandle); + XML_SetCharacterDataHandler(parser, BloodcatsCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + XML_SetUserData(parser, &pData); + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + sprintf(errorBuf, "XML Parser Error in BloodcatPlacements.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + XML_ParserFree(parser); + + return TRUE; +} diff --git a/Strategic/XML_DynamicRestrictions.cpp b/Strategic/XML_DynamicRestrictions.cpp new file mode 100644 index 00000000..f0cd8340 --- /dev/null +++ b/Strategic/XML_DynamicRestrictions.cpp @@ -0,0 +1,223 @@ +/////////////////////////////////////////////////////////////////////////////// +// HEADROCK HAM 3.4: Dynamic Roaming Restrictions XMLization +// +// This is a new part of the Dynamic Roaming Restrictions system. It reads in +// data about sectors to "un-restrict", and the towns that must be liberated +// for them to be unrestricted. There can be several unrestriction conditions +// for each sector, and only one of the conditions must be true for the sector +// to become allowed. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "Debug Control.h" + #include "expat.h" + #include "gamesettings.h" + #include "XML.h" + #include "FileMan.h" + #include "campaign types.h" + #include "militiasquads.h" + #include "mapscreen.h" +#endif + +// HEADROCK HAM 3.4: Stores restriction data from XML, including required cities for each sector to be allowed. +extern DYNAMICRESTRICTIONS gDynamicRestrictions[5001]; + +struct +{ + PARSE_STAGE curElement; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + INT16 sCurSectorX; + INT16 sCurSectorY; + UINT32 uiReqTownFlags; + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef dynamicroamingParseData; + + +static void XMLCALL +dynamicroamingStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + dynamicroamingParseData * pData = (dynamicroamingParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + if(strcmp(name, "DYNAMICROAMING") == 0 && pData->curElement == ELEMENT_NONE) + { + // Set all sector IDs to -1 ("unused") + for (UINT16 cnt=0; cnt<5001; cnt++) + { + gDynamicRestrictions[cnt].bSectorID = -1; + } + pData->curElement = ELEMENT_LIST; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "RESTRICTION") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = ELEMENT; + pData->uiReqTownFlags = 0; + + //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"MergeStartElementHandle: setting memory for curMerge"); + + pData->maxReadDepth++; //we are not skipping this element + + //pData->curIndex++; + } + else if(pData->curElement == ELEMENT && + (strcmp(name, "SectorGrid") == 0 || + strcmp(name, "ReqTown") == 0 )) + { + pData->curElement = ELEMENT_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +dynamicroamingCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + dynamicroamingParseData * pData = (dynamicroamingParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +dynamicroamingEndElementHandle(void *userData, const XML_Char *name) +{ + dynamicroamingParseData * pData = (dynamicroamingParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "DYNAMICROAMING") == 0) + { + pData->curElement = ELEMENT_NONE; + } + else if(strcmp(name, "RESTRICTION") == 0) + { + pData->curElement = ELEMENT_LIST; + + if (SECTOR(pData->sCurSectorX, pData->sCurSectorY) >= 0 && SECTOR(pData->sCurSectorX, pData->sCurSectorY) < 256) + { + pData->curIndex++; + gDynamicRestrictions[pData->curIndex].bSectorID = SECTOR(pData->sCurSectorX,pData->sCurSectorY); + gDynamicRestrictions[pData->curIndex].uiReqTownFlags |= pData->uiReqTownFlags; + } + } + + else if(strcmp(name, "SectorGrid") == 0 ) + { + UINT8 x, y; + pData->curElement = ELEMENT; + + y = (UINT8)pData->szCharData[0] & 0x1F; + x = (UINT8)atol(&pData->szCharData[1]); + if ( x > 0 && x <= 16 && y > 0 && y <= 16 ) + { + pData->sCurSectorX = x; + pData->sCurSectorY = y; + } + } + else if(strcmp(name, "ReqTown") == 0 ) + { + if ((UINT8) atol(pData->szCharData) > 0 && (UINT8) atol(pData->szCharData) < NUM_TOWNS ) + { + pData->curElement = ELEMENT; + pData->uiReqTownFlags |= (1 << ((UINT8) atol(pData->szCharData) - 1)); + } + else + { + pData->uiReqTownFlags = 0; + } + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + + + +BOOLEAN ReadInDynamicRoamingRestrictions(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + dynamicroamingParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading DynamicRestrictions.xml" ); + + // Open merges file + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, dynamicroamingStartElementHandle, dynamicroamingEndElementHandle); + XML_SetCharacterDataHandler(parser, dynamicroamingCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + pData.maxArraySize = MAXITEMS; + pData.curIndex = -1; + + XML_SetUserData(parser, &pData); + + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in DynamicRestrictions.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + return( TRUE ); +} + diff --git a/Strategic/XML_Facilities.cpp b/Strategic/XML_Facilities.cpp new file mode 100644 index 00000000..630b4e8d --- /dev/null +++ b/Strategic/XML_Facilities.cpp @@ -0,0 +1,217 @@ +/////////////////////////////////////////////////////////////////////////////// +// HEADROCK HAM 3.5: Facility Location Externalization +// +// This file reads from an XML called "Facilities.XML". That file contains +// the locations of all facilities such as the "Gun Range", "Industrial", +// "Prison", etcetera. The data is written DIRECTLY into the global SectorInfo +// array, replacing a hardcoded system. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "Debug Control.h" + #include "expat.h" + #include "gamesettings.h" + #include "XML.h" + #include "FileMan.h" + #include "Campaign Types.h" +#endif + +FACILITYLOCATIONS gFacilityLocations[256][MAX_NUM_FACILITY_TYPES]; +UINT16 NUM_FACILITIES; + +struct +{ + PARSE_STAGE curElement; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + INT16 sCurSectorX; + INT16 sCurSectorY; + UINT8 ubCurFacilityType; + UINT8 ubHidden; + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef facilityParseData; + + +static void XMLCALL +facilityStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + facilityParseData * pData = (facilityParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + if(strcmp(name, "SECTORFACILITIES") == 0 && pData->curElement == ELEMENT_NONE) + { + pData->curElement = ELEMENT_LIST; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "FACILITY") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = ELEMENT; + + //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"MergeStartElementHandle: setting memory for curMerge"); + + pData->maxReadDepth++; //we are not skipping this element + + //pData->curIndex++; + } + else if(pData->curElement == ELEMENT && + (strcmp(name, "SectorGrid") == 0 || + strcmp(name, "FacilityType") == 0 || + strcmp(name, "ubHidden") == 0 + )) + { + pData->curElement = ELEMENT_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +facilityCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + facilityParseData * pData = (facilityParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +facilityEndElementHandle(void *userData, const XML_Char *name) +{ + facilityParseData * pData = (facilityParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "SECTORFACILITIES") == 0) + { + pData->curElement = ELEMENT_NONE; + NUM_FACILITIES = pData->curIndex; + } + else if(strcmp(name, "FACILITY") == 0) + { + pData->curIndex++; + pData->curElement = ELEMENT_LIST; + + if (pData->ubCurFacilityType != 0) + { + gFacilityLocations[SECTOR(pData->sCurSectorX,pData->sCurSectorY)][pData->ubCurFacilityType].fFacilityHere = TRUE; + gFacilityLocations[SECTOR(pData->sCurSectorX,pData->sCurSectorY)][pData->ubCurFacilityType].ubHidden = pData->ubHidden; + } + } + + else if(strcmp(name, "SectorGrid") == 0 ) + { + UINT8 x, y; + pData->curElement = ELEMENT; + + y = (UINT8)pData->szCharData[0] & 0x1F; + x = (UINT8)atol(&pData->szCharData[1]); + if ( x > 0 && x <= 16 && y > 0 && y <= 16 ) + { + pData->sCurSectorX = x; + pData->sCurSectorY = y; + } + } + else if(strcmp(name, "FacilityType") == 0 ) + { + pData->curElement = ELEMENT; + pData->ubCurFacilityType = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubHidden") == 0 ) + { + pData->curElement = ELEMENT; + pData->ubHidden = (BOOLEAN) atol(pData->szCharData); + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + + + +BOOLEAN ReadInSectorFacilities(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + facilityParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading Facilities.xml" ); + + // Open merges file + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, facilityStartElementHandle, facilityEndElementHandle); + XML_SetCharacterDataHandler(parser, facilityCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + pData.maxArraySize = MAXITEMS; + pData.curIndex = -1; + + XML_SetUserData(parser, &pData); + + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in Facilities.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + return( TRUE ); +} + diff --git a/Strategic/XML_FacilityTypes.cpp b/Strategic/XML_FacilityTypes.cpp new file mode 100644 index 00000000..66270d4a --- /dev/null +++ b/Strategic/XML_FacilityTypes.cpp @@ -0,0 +1,1117 @@ +/////////////////////////////////////////////////////////////////////////////// +// HEADROCK HAM 3.5: Facility Type Externalization +// +// This file reads XML data from FacilityTypes.XML and stores it in an array. +// It tells the game which bonuses or penalties are given for performing +// specific actions or even just being present in a sector. Later in the game, +// the appropriate functions will read this data to change strategic factors, +// like the speed of training or the ability to train militia. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "Debug Control.h" + #include "expat.h" + #include "gamesettings.h" + #include "XML.h" + #include "FileMan.h" + #include "Campaign Types.h" + #include "Facilities.h" +#endif + +FACILITYTYPE gFacilityTypes[ MAX_NUM_FACILITY_TYPES ]; +UINT16 NUM_FACILITY_TYPES = 0; + +typedef enum +{ + FACILITYTYPE_NONE = 0, + FACILITYTYPE_LIST, + FACILITYTYPE_TYPE, + FACILITYTYPE_ELEMENT, + FACILITYTYPE_ASSIGNMENT, + FACILITYTYPE_ASSIGNMENT_ELEMENT, + FACILITYTYPE_CONDITIONS, + FACILITYTYPE_CONDITIONS_ELEMENT, + FACILITYTYPE_RISK, + FACILITYTYPE_RISK_ELEMENT, + +} FACILITYTYPE_PARSE_STAGE; + +struct +{ + FACILITYTYPE_PARSE_STAGE curElement; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + FACILITYTYPE curFacilityTypeData; + FACILITYASSIGNMENTTYPE curAssignmentData; + INT16 curAssignmentType; + INT16 curRisk; + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef facilitytypeParseData; + + +void InitFacilityTypeEntry( facilitytypeParseData *pData ) +{ + + // This does base setup on a single type entry, making sure that all values are set to DEFAULT. This is run + // immediately before writing XML data into game memory. + // Default values set here are only those that differ from 0. Normally this means maximums and performace data + // which should equal 100 unless defined otherwise in the XML. + + pData->curFacilityTypeData.usMilitiaTraining = 100; + pData->curFacilityTypeData.usMobileMilitiaTraining = 100; + + for (UINT16 cnt = 0; cnt < NUM_FACILITY_ASSIGNMENTS; cnt++ ) + { + pData->curFacilityTypeData.AssignmentData[ cnt ].ubMaximumBreath = 100; + pData->curFacilityTypeData.AssignmentData[ cnt ].ubMaximumMorale = 100; + pData->curFacilityTypeData.AssignmentData[ cnt ].usKitDegrade = 100; + pData->curFacilityTypeData.AssignmentData[ cnt ].usFatigue = 100; + pData->curFacilityTypeData.AssignmentData[ cnt ].usPerformance = 100; + pData->curFacilityTypeData.AssignmentData[ cnt ].usSleep = 100; + } +} + +void InitAssignmentDataArray( facilitytypeParseData *pData ) +{ + + // Set assignment data from memory + pData->curAssignmentData.usPerformance = 100; + pData->curAssignmentData.ubStaffLimit = 0; + swprintf(pData->curAssignmentData.szTooltipText, L""); + pData->curAssignmentData.sCostPerHour = 0; + + pData->curAssignmentData.usFatigue = 100; + pData->curAssignmentData.usSleep = 100; + pData->curAssignmentData.usKitDegrade = 100; + + pData->curAssignmentData.sSkyriderCostModifier = 0; + pData->curAssignmentData.usMineIncomeModifier = 100; + pData->curAssignmentData.fOnlyLocalMineAffected = 0; + + pData->curAssignmentData.ubMaximumBreath = 100; + pData->curAssignmentData.ubMaximumMorale = 100; + + pData->curAssignmentData.fCountEnemiesInCities = 0; + pData->curAssignmentData.fCountEnemiesInWild = 0; + pData->curAssignmentData.fDetectEnemiesAnywhere = 0; + pData->curAssignmentData.fDetectEnemiesDynamic = 0; + pData->curAssignmentData.ubDetectEnemiesImmediate = 0; + pData->curAssignmentData.fDetectEnemiesLongrange = 0; + + pData->curAssignmentData.ubMinimumAgility = 0; + pData->curAssignmentData.ubMinimumBreath = 0; + pData->curAssignmentData.ubMinimumDexterity = 0; + pData->curAssignmentData.ubMinimumExplosives = 0; + pData->curAssignmentData.ubMinimumHealth = 0; + pData->curAssignmentData.ubMinimumLeadership = 0; + pData->curAssignmentData.ubMinimumLevel = 0; + pData->curAssignmentData.ubMinimumLoyaltyHere = 0; + pData->curAssignmentData.ubMinimumMarksmanship = 0; + pData->curAssignmentData.ubMinimumMechanical = 0; + pData->curAssignmentData.ubMinimumMedical = 0; + pData->curAssignmentData.ubMinimumMorale = 0; + pData->curAssignmentData.ubMinimumStrength = 0; + pData->curAssignmentData.ubMinimumWisdom = 0; + + // Set RISK data + for (UINT16 cnt = 0; cnt < NUM_RISKS; cnt++) + { + pData->curAssignmentData.Risk[cnt].ubChance = 0; + pData->curAssignmentData.Risk[cnt].bBaseEffect = 0; + pData->curAssignmentData.Risk[cnt].ubRange = 0; + } + +} + + +static void XMLCALL +facilitytypeStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + facilitytypeParseData * pData = (facilitytypeParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + // Just starting + if(strcmp(name, "FACILITYTYPES") == 0 && pData->curElement == FACILITYTYPE_NONE) + { + pData->curElement = FACILITYTYPE_LIST; + + pData->maxReadDepth++; //we are not skipping this element + } + + // Encountered type entry + else if(strcmp(name, "FACILITYTYPE") == 0 && pData->curElement == FACILITYTYPE_LIST) + { + pData->curElement = FACILITYTYPE_TYPE; + + memset( &pData->curFacilityTypeData, 0, sizeof(FACILITYTYPE) ); + + // Set all values to default before applying XML data + InitFacilityTypeEntry( pData ); + + //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"MergeStartElementHandle: setting memory for curMerge"); + + pData->maxReadDepth++; //we are not skipping this element + + //pData->curIndex++; + } + + // Encountered basic type data + else if(pData->curElement == FACILITYTYPE_TYPE && + (strcmp(name, "ubIndex") == 0 || + strcmp(name, "szFacilityName") == 0 || + strcmp(name, "szFacilityShortName") == 0 || + strcmp(name, "ubTotalStaffLimit") == 0 || + + strcmp(name, "ubMilitiaTrainersAllowed") == 0 || + strcmp(name, "ubMobileMilitiaTrainersAllowed") == 0 || + strcmp(name, "usMilitiaTraining") == 0 || + strcmp(name, "usMobileMilitiaTraining") == 0 )) + { + pData->curElement = FACILITYTYPE_ELEMENT; + + pData->maxReadDepth++; //we are not skipping this element + } + + // Encountered Assignment entry + else if(pData->curElement == FACILITYTYPE_TYPE && + strcmp(name, "ASSIGNMENT") == 0) + { + // Set value to -1, denoting that we have not yet read which assignment is being described. + pData->curAssignmentType = -1; + // Reset the array + InitAssignmentDataArray (pData); + + pData->curElement = FACILITYTYPE_ASSIGNMENT; + + pData->maxReadDepth++; + } + + // Encountered assignment data + else if(pData->curElement == FACILITYTYPE_ASSIGNMENT && + (strcmp(name, "ubAssignmentType") == 0 || + strcmp(name, "szTooltipText") == 0 || + strcmp(name, "ubStaffLimit") == 0 || + strcmp(name, "sCostPerHour") == 0 || + strcmp(name, "usPerformance") == 0 || + strcmp(name, "usFatigue") == 0 || + strcmp(name, "usSleep") == 0 || + strcmp(name, "usKitDegrade") == 0 || + strcmp(name, "ubMaximumMorale") == 0 || + strcmp(name, "ubMaximumFatigue") == 0 || + strcmp(name, "ubDetectEnemiesImmediate") == 0 || + strcmp(name, "fDetectEnemiesDynamic") == 0 || + strcmp(name, "fDetectEnemiesLongrange") == 0 || + strcmp(name, "fDetectEnemiesAnywhere") == 0 || + strcmp(name, "fCountEnemiesInWild") == 0 || + strcmp(name, "fCountEnemiesInCities") == 0 || + strcmp(name, "sSkyriderCostModifier") == 0 || + strcmp(name, "usMineIncomeModifier") == 0 || + strcmp(name, "fOnlyLocalMineAffected") == 0 )) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT_ELEMENT; + + pData->maxReadDepth++; + } + else if(pData->curElement == FACILITYTYPE_ASSIGNMENT && + strcmp(name, "CONDITIONS") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + + pData->maxReadDepth++; + } + // Check for entering into a risk tag. Assign value to current risk. + else if(pData->curElement == FACILITYTYPE_ASSIGNMENT) + { + if (strcmp(name, "STRENGTH") == 0) + { + pData->curRisk = RISK_STRENGTH; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "WISDOM") == 0) + { + pData->curRisk = RISK_WISDOM; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "AGILITY") == 0) + { + pData->curRisk = RISK_AGILITY; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "DEXTERITY") == 0) + { + pData->curRisk = RISK_DEXTERITY; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "HEALTH") == 0) + { + pData->curRisk = RISK_HEALTH; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "MARKSMANSHIP") == 0) + { + pData->curRisk = RISK_MARKSMANSHIP; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "LEADERSHIP") == 0) + { + pData->curRisk = RISK_LEADERSHIP; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "EXPLOSIVES") == 0) + { + pData->curRisk = RISK_EXPLOSIVES; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "MECHANICAL") == 0) + { + pData->curRisk = RISK_MECHANICAL; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "MEDICAL") == 0) + { + pData->curRisk = RISK_MEDICAL; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "INJURY") == 0) + { + pData->curRisk = RISK_INJURY; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "MORALE") == 0) + { + pData->curRisk = RISK_MORALE; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "FATIGUE") == 0) + { + pData->curRisk = RISK_FATIGUE; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "LOYALTY_LOCAL") == 0) + { + pData->curRisk = RISK_LOYALTY_LOCAL; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "LOYALTY_GLOBAL") == 0) + { + pData->curRisk = RISK_LOYALTY_GLOBAL; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + if (strcmp(name, "DRUNK") == 0) + { + pData->curRisk = RISK_DRUNK; + pData->curElement = FACILITYTYPE_RISK; + pData->maxReadDepth++; + } + } + + else if(pData->curElement == FACILITYTYPE_CONDITIONS && + (strcmp(name, "ubMinimumStrength") == 0 || + strcmp(name, "ubMinimumHealth") == 0 || + strcmp(name, "ubMinimumWisdom") == 0 || + strcmp(name, "ubMinimumAgility") == 0 || + strcmp(name, "ubMinimumDexterity") == 0 || + strcmp(name, "ubMinimumMarksmanship") == 0 || + strcmp(name, "ubMinimumLeadership") == 0 || + strcmp(name, "ubMinimumExplosives") == 0 || + strcmp(name, "ubMinimumMechanical") == 0 || + strcmp(name, "ubMinimumMedical") == 0 || + strcmp(name, "ubMinimumLevel") == 0 || + strcmp(name, "ubMinimumLoyaltyHere") == 0 || + strcmp(name, "ubMinimumBreath") == 0 || + strcmp(name, "ubMinimumMorale") == 0 ) ) + { + pData->curElement = FACILITYTYPE_CONDITIONS_ELEMENT; + + pData->maxReadDepth++; + } + + else if(pData->curElement == FACILITYTYPE_RISK && + (strcmp(name, "ubChance") == 0 || + strcmp(name, "bBaseEffect") == 0 || + strcmp(name, "ubRange") == 0 ) ) + { + pData->curElement = FACILITYTYPE_RISK_ELEMENT; + + pData->maxReadDepth++; + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +facilitytypeCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + facilitytypeParseData * pData = (facilitytypeParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +facilitytypeEndElementHandle(void *userData, const XML_Char *name) +{ + facilitytypeParseData * pData = (facilitytypeParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "FACILITYTYPES") == 0) + { + if (pData->curElement == FACILITYTYPE_LIST) + { + pData->curElement = FACILITYTYPE_NONE; + NUM_FACILITY_TYPES++; + } + else + { + THROWEXCEPTION(L"FacilityTypes.XML Error: Tag encountered too early! Please make sure to close all opened tags first."); + } + } + else if(strcmp(name, "FACILITYTYPE") == 0) + { + if (pData->curElement == FACILITYTYPE_TYPE) + { + pData->curElement = FACILITYTYPE_LIST; + + if (pData->curIndex >= 1) + { + + // Begin with basic facility data + wcscpy(gFacilityTypes[pData->curIndex].szFacilityName, pData->curFacilityTypeData.szFacilityName); + wcscpy(gFacilityTypes[pData->curIndex].szFacilityShortName, pData->curFacilityTypeData.szFacilityShortName); + gFacilityTypes[pData->curIndex].ubTotalStaffLimit = pData->curFacilityTypeData.ubTotalStaffLimit; + + gFacilityTypes[pData->curIndex].ubMilitiaTrainersAllowed = pData->curFacilityTypeData.ubMilitiaTrainersAllowed; + gFacilityTypes[pData->curIndex].ubMobileMilitiaTrainersAllowed = pData->curFacilityTypeData.ubMobileMilitiaTrainersAllowed; + gFacilityTypes[pData->curIndex].usMilitiaTraining = pData->curFacilityTypeData.usMilitiaTraining; + gFacilityTypes[pData->curIndex].usMilitiaTraining = pData->curFacilityTypeData.usMilitiaTraining; + + // Set assignment-specific data + for (UINT16 cnt = 0; cnt < NUM_FACILITY_ASSIGNMENTS; cnt++) + { + // Performance and limits + gFacilityTypes[pData->curIndex].AssignmentData[cnt].usPerformance = pData->curFacilityTypeData.AssignmentData[cnt].usPerformance; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubStaffLimit = pData->curFacilityTypeData.AssignmentData[cnt].ubStaffLimit; + wcscpy(gFacilityTypes[pData->curIndex].AssignmentData[cnt].szTooltipText, pData->curFacilityTypeData.AssignmentData[cnt].szTooltipText); + gFacilityTypes[pData->curIndex].AssignmentData[cnt].sCostPerHour = pData->curFacilityTypeData.AssignmentData[cnt].sCostPerHour; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].usFatigue = pData->curFacilityTypeData.AssignmentData[cnt].usFatigue; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].usKitDegrade = pData->curFacilityTypeData.AssignmentData[cnt].usKitDegrade; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].usSleep = pData->curFacilityTypeData.AssignmentData[cnt].usSleep; + + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMaximumBreath = pData->curFacilityTypeData.AssignmentData[cnt].ubMaximumBreath; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMaximumMorale = pData->curFacilityTypeData.AssignmentData[cnt].ubMaximumMorale; + + // Detection abilities + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubDetectEnemiesImmediate = pData->curFacilityTypeData.AssignmentData[cnt].ubDetectEnemiesImmediate; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].fDetectEnemiesDynamic = pData->curFacilityTypeData.AssignmentData[cnt].fDetectEnemiesDynamic; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].fDetectEnemiesLongrange = pData->curFacilityTypeData.AssignmentData[cnt].fDetectEnemiesLongrange; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].fDetectEnemiesAnywhere = pData->curFacilityTypeData.AssignmentData[cnt].fDetectEnemiesAnywhere; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].fCountEnemiesInWild = pData->curFacilityTypeData.AssignmentData[cnt].fCountEnemiesInWild; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].fCountEnemiesInCities = pData->curFacilityTypeData.AssignmentData[cnt].fCountEnemiesInCities; + + // Other strategic abilities + gFacilityTypes[pData->curIndex].AssignmentData[cnt].sSkyriderCostModifier = pData->curFacilityTypeData.AssignmentData[cnt].sSkyriderCostModifier; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].usMineIncomeModifier = pData->curFacilityTypeData.AssignmentData[cnt].usMineIncomeModifier; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].fOnlyLocalMineAffected = pData->curFacilityTypeData.AssignmentData[cnt].fOnlyLocalMineAffected; + + // Conditions + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumStrength = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumStrength; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumAgility = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumAgility; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumDexterity = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumDexterity; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumHealth = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumHealth; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumWisdom = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumWisdom; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumMarksmanship = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumMarksmanship; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumLeadership = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumLeadership; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumExplosives = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumExplosives; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumMechanical = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumMechanical; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumMedical = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumMedical; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumLevel = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumLevel; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].ubMinimumLoyaltyHere = pData->curFacilityTypeData.AssignmentData[cnt].ubMinimumLoyaltyHere; + + // Set risks associated with this assignment + for (UINT16 cntB = 0; cntB < NUM_RISKS; cntB++) + { + gFacilityTypes[pData->curIndex].AssignmentData[cnt].Risk[cntB].ubChance = pData->curFacilityTypeData.AssignmentData[cnt].Risk[cntB].ubChance; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].Risk[cntB].bBaseEffect = pData->curFacilityTypeData.AssignmentData[cnt].Risk[cntB].bBaseEffect; + gFacilityTypes[pData->curIndex].AssignmentData[cnt].Risk[cntB].ubRange = pData->curFacilityTypeData.AssignmentData[cnt].Risk[cntB].ubRange; + } + } + + pData->curIndex = 0; // Done with this index. + NUM_FACILITY_TYPES++; // Count number of facility types successfully loaded into the game + } + } + else + { + THROWEXCEPTION(L"FacilityTypes.XML Error: A tag was encountered too early! Please make sure all tags are closed properly."); + } + } + + // Set current Index. As long as it equals 0, the program is not writing any data to memory. + else if(strcmp(name, "ubIndex") == 0) + { + pData->curElement = FACILITYTYPE_TYPE; + pData->curIndex = (UINT8) atol(pData->szCharData); + } + + + ////////////////////////////////////////////// + // General facility data and militia effects + + else if(strcmp(name, "szFacilityName") == 0) + { + pData->curElement = FACILITYTYPE_TYPE; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curFacilityTypeData.szFacilityName, sizeof(pData->curFacilityTypeData.szFacilityName)/sizeof(pData->curFacilityTypeData.szFacilityName[0]) ); + pData->curFacilityTypeData.szFacilityName[sizeof(pData->curFacilityTypeData.szFacilityName)/sizeof(pData->curFacilityTypeData.szFacilityName[0]) - 1] = '\0'; + } + + else if(strcmp(name, "szFacilityShortName") == 0) + { + pData->curElement = FACILITYTYPE_TYPE; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curFacilityTypeData.szFacilityShortName, sizeof(pData->curFacilityTypeData.szFacilityShortName)/sizeof(pData->curFacilityTypeData.szFacilityShortName[0]) ); + pData->curFacilityTypeData.szFacilityShortName[sizeof(pData->curFacilityTypeData.szFacilityShortName)/sizeof(pData->curFacilityTypeData.szFacilityShortName[0]) - 1] = '\0'; + } + + else if(strcmp(name, "ubTotalStaffLimit") == 0 ) + { + pData->curElement = FACILITYTYPE_TYPE; + pData->curFacilityTypeData.ubTotalStaffLimit = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMilitiaTrainersAllowed") == 0 ) + { + pData->curElement = FACILITYTYPE_TYPE; + pData->curFacilityTypeData.ubMilitiaTrainersAllowed = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMobileMilitiaTrainersAllowed") == 0 ) + { + pData->curElement = FACILITYTYPE_TYPE; + pData->curFacilityTypeData.ubMobileMilitiaTrainersAllowed = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "usMilitiaTraining") == 0 ) + { + pData->curElement = FACILITYTYPE_TYPE; + pData->curFacilityTypeData.usMilitiaTraining = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "usMobileMilitiaTraining") == 0 ) + { + pData->curElement = FACILITYTYPE_TYPE; + pData->curFacilityTypeData.usMobileMilitiaTraining = (UINT8) atol(pData->szCharData); + } + + /////////////////////////////////////////////////////// + // Assignments + + else if(strcmp(name, "ASSIGNMENT") == 0 ) + { + pData->curElement = FACILITYTYPE_TYPE; + // Make sure an assignment is selected for data dump + if (pData->curAssignmentType == -1) + { + CHAR16 sErrorString[256]; + swprintf(sErrorString, L"FacilityTypes.XML Error: Assignment data for facility type %d is missing a tag", pData->curIndex); + THROWEXCEPTION(sErrorString); + } + else if (pData->curAssignmentData.ubStaffLimit <= 0 && + pData->curAssignmentType != FAC_AMBIENT ) + { + CHAR16 sErrorString[256]; + swprintf(sErrorString, L"FacilityTypes.XML Error: Assignment data for facility type %d is missing a tag, or the value of the tag is 0.", pData->curIndex); + THROWEXCEPTION(sErrorString); + } + else + { + // Set assignment data from memory + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].usPerformance = pData->curAssignmentData.usPerformance; + wcscpy(pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].szTooltipText, pData->curAssignmentData.szTooltipText); + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubStaffLimit = pData->curAssignmentData.ubStaffLimit; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].sCostPerHour = pData->curAssignmentData.sCostPerHour; + + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].usFatigue = pData->curAssignmentData.usFatigue; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].usSleep = pData->curAssignmentData.usSleep; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].usKitDegrade = pData->curAssignmentData.usKitDegrade; + + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].sSkyriderCostModifier = pData->curAssignmentData.sSkyriderCostModifier; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].usMineIncomeModifier = pData->curAssignmentData.usMineIncomeModifier; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].fOnlyLocalMineAffected = pData->curAssignmentData.fOnlyLocalMineAffected; + + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMaximumBreath = pData->curAssignmentData.ubMaximumBreath; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMaximumMorale = pData->curAssignmentData.ubMaximumMorale; + + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].fCountEnemiesInCities = pData->curAssignmentData.fCountEnemiesInCities; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].fCountEnemiesInWild = pData->curAssignmentData.fCountEnemiesInWild; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].fDetectEnemiesAnywhere = pData->curAssignmentData.fDetectEnemiesAnywhere; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].fDetectEnemiesDynamic = pData->curAssignmentData.fDetectEnemiesDynamic; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubDetectEnemiesImmediate = pData->curAssignmentData.ubDetectEnemiesImmediate; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].fDetectEnemiesLongrange = pData->curAssignmentData.fDetectEnemiesLongrange; + + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumAgility = pData->curAssignmentData.ubMinimumAgility; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumBreath = pData->curAssignmentData.ubMinimumBreath; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumDexterity = pData->curAssignmentData.ubMinimumDexterity; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumExplosives = pData->curAssignmentData.ubMinimumExplosives; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumHealth = pData->curAssignmentData.ubMinimumHealth; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumLeadership = pData->curAssignmentData.ubMinimumLeadership; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumLevel = pData->curAssignmentData.ubMinimumLevel; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumLoyaltyHere = pData->curAssignmentData.ubMinimumLoyaltyHere; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumMarksmanship = pData->curAssignmentData.ubMinimumMarksmanship; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumMechanical = pData->curAssignmentData.ubMinimumMechanical; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumMedical = pData->curAssignmentData.ubMinimumMedical; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumMorale = pData->curAssignmentData.ubMinimumMorale; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumStrength = pData->curAssignmentData.ubMinimumStrength; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].ubMinimumWisdom = pData->curAssignmentData.ubMinimumWisdom; + + // Set RISK data + for (UINT16 cnt = 0; cnt < NUM_RISKS; cnt++) + { + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].Risk[cnt].ubChance = pData->curAssignmentData.Risk[cnt].ubChance; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].Risk[cnt].bBaseEffect = pData->curAssignmentData.Risk[cnt].bBaseEffect; + pData->curFacilityTypeData.AssignmentData[pData->curAssignmentType].Risk[cnt].ubRange = pData->curAssignmentData.Risk[cnt].ubRange; + } + } + } + + //////////////////////////////////////////////// + // Specific Assignment Data + + else if(strcmp(name, "ubAssignmentType") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + // Set the assignment type we're currently reading. This is important, as if this does not occur, + // the program doesn't know where to put the rest of the assignment data, and will throw an on-screen error + // as a result. + if (strcmp(pData->szCharData, "AMBIENT") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_AMBIENT; + } + else if (strcmp(pData->szCharData, "STAFF") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STAFF; + } + else if (strcmp(pData->szCharData, "REST") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_REST; + } + else if (strcmp(pData->szCharData, "DOCTOR") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_DOCTOR; + } + else if (strcmp(pData->szCharData, "PATIENT") == 0) + { + pData->curAssignmentType = FAC_PATIENT; + } + else if (strcmp(pData->szCharData, "REPAIR_ITEMS") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_REPAIR_ITEMS; + } + else if (strcmp(pData->szCharData, "REPAIR_VEHICLE") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_REPAIR_VEHICLE; + } + else if (strcmp(pData->szCharData, "REPAIR_ROBOT") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_REPAIR_ROBOT; + } + else if (strcmp(pData->szCharData, "PRACTICE_STRENGTH") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_STRENGTH; + } + else if (strcmp(pData->szCharData, "PRACTICE_DEXTERITY") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_DEXTERITY; + } + else if (strcmp(pData->szCharData, "PRACTICE_AGILITY") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_AGILITY; + } + else if (strcmp(pData->szCharData, "PRACTICE_HEALTH") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_HEALTH; + } + else if (strcmp(pData->szCharData, "PRACTICE_MARKSMANSHIP") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_MARKSMANSHIP; + } + else if (strcmp(pData->szCharData, "PRACTICE_LEADERSHIP") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_LEADERSHIP; + } + else if (strcmp(pData->szCharData, "PRACTICE_MEDICAL") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_MEDICAL; + } + else if (strcmp(pData->szCharData, "PRACTICE_MECHANICAL") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_MECHANICAL; + } + else if (strcmp(pData->szCharData, "PRACTICE_EXPLOSIVES") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_PRACTICE_EXPLOSIVES; + } + else if (strcmp(pData->szCharData, "TRAINER_STRENGTH") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_STRENGTH; + } + else if (strcmp(pData->szCharData, "TRAINER_DEXTERITY") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_DEXTERITY; + } + else if (strcmp(pData->szCharData, "TRAINER_AGILITY") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_AGILITY; + } + else if (strcmp(pData->szCharData, "TRAINER_HEALTH") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_HEALTH; + } + else if (strcmp(pData->szCharData, "TRAINER_MARKSMANSHIP") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_MARKSMANSHIP; + } + else if (strcmp(pData->szCharData, "TRAINER_LEADERSHIP") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_LEADERSHIP; + } + else if (strcmp(pData->szCharData, "TRAINER_MECHANICAL") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_MECHANICAL; + } + else if (strcmp(pData->szCharData, "TRAINER_MEDICAL") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_MEDICAL; + } + else if (strcmp(pData->szCharData, "TRAINER_EXPLOSIVES") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_TRAINER_EXPLOSIVES; + } + else if (strcmp(pData->szCharData, "STUDENT_STRENGTH") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_STRENGTH; + } + else if (strcmp(pData->szCharData, "STUDENT_DEXTERITY") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_DEXTERITY; + } + else if (strcmp(pData->szCharData, "STUDENT_AGILITY") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_AGILITY; + } + else if (strcmp(pData->szCharData, "STUDENT_HEALTH") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_HEALTH; + } + else if (strcmp(pData->szCharData, "STUDENT_MARKSMANSHIP") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_MARKSMANSHIP; + } + else if (strcmp(pData->szCharData, "STUDENT_LEADERSHIP") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_LEADERSHIP; + } + else if (strcmp(pData->szCharData, "STUDENT_MECHANICAL") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_MECHANICAL; + } + else if (strcmp(pData->szCharData, "STUDENT_MEDICAL") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_MEDICAL; + } + else if (strcmp(pData->szCharData, "STUDENT_EXPLOSIVES") == 0) + { + //pData->curAssignmentType = (INT16) atol(pData->szCharData); + pData->curAssignmentType = FAC_STUDENT_EXPLOSIVES; + } + else + { + CHAR16 sErrorString[256]; + swprintf(sErrorString, L"FacilityTypes.XML Error: Assignment type for facility %d is unrecognized!", pData->curIndex); + THROWEXCEPTION(sErrorString); + } + } + + else if(strcmp(name, "ubStaffLimit") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.ubStaffLimit = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "szTooltipText") == 0) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curAssignmentData.szTooltipText, sizeof(pData->curAssignmentData.szTooltipText)/sizeof(pData->curAssignmentData.szTooltipText[0]) ); + pData->curAssignmentData.szTooltipText[sizeof(pData->curAssignmentData.szTooltipText)/sizeof(pData->curAssignmentData.szTooltipText[0]) - 1] = '\0'; + } + + else if(strcmp(name, "sCostPerHour") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.sCostPerHour = (INT16) atol(pData->szCharData); + } + + else if(strcmp(name, "usPerformance") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.usPerformance = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "usFatigue") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.usFatigue = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "usSleep") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.usSleep = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "usKitDegrade") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.usKitDegrade = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMaximumMorale") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.ubMaximumMorale = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMaximumBreath") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.ubMaximumBreath = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubDetectEnemiesImmediate") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.ubDetectEnemiesImmediate = (BOOLEAN) atol(pData->szCharData); + } + + else if(strcmp(name, "fDetectEnemiesDynamic") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.fDetectEnemiesDynamic = (BOOLEAN) atol(pData->szCharData); + } + + else if(strcmp(name, "fDetectEnemiesLongrange") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.fDetectEnemiesLongrange = (BOOLEAN) atol(pData->szCharData); + } + + else if(strcmp(name, "fDetectEnemiesAnywhere") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.fDetectEnemiesAnywhere = (BOOLEAN) atol(pData->szCharData); + } + + else if(strcmp(name, "fCountEnemiesInWild") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.fCountEnemiesInWild = (BOOLEAN) atol(pData->szCharData); + } + + else if(strcmp(name, "fCountEnemiesInCities") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.fCountEnemiesInCities = (BOOLEAN) atol(pData->szCharData); + } + + else if(strcmp(name, "sSkyriderCostModifier") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.sSkyriderCostModifier = (INT16) atol(pData->szCharData); + } + + else if(strcmp(name, "usMineIncomeModifier") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.usMineIncomeModifier = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "fOnlyLocalMineAffected") == 0 ) + { + pData->curElement = FACILITYTYPE_ASSIGNMENT; + pData->curAssignmentData.fOnlyLocalMineAffected = (BOOLEAN) atol(pData->szCharData); + } + + ////////////////////////////////////////////// + // Conditions for assignment + + else if(strcmp(name, "CONDITIONS") == 0) + { + // No data write, just downgrade parse stage. + pData->curElement = FACILITYTYPE_ASSIGNMENT; + } + + else if(strcmp(name, "ubMinimumStrength") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumStrength = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumHealth") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumHealth = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumWisdom") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumWisdom = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumAgility") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumAgility = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumDexterity") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumDexterity = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumMarksmanship") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumMarksmanship = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumLeadership") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumLeadership = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumExplosives") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumExplosives = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumMechanical") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumMechanical = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumMedical") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumMedical = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumLevel") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumLevel = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumLoyaltyHere") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumLoyaltyHere = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumBreath") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumBreath = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubMinimumMorale") == 0 ) + { + pData->curElement = FACILITYTYPE_CONDITIONS; + pData->curAssignmentData.ubMinimumMorale = (UINT8) atol(pData->szCharData); + } + + //////////////////////////////////////// + // Risk data for assignment + + else if(strcmp(name, "STRENGTH") == 0 || + strcmp(name, "AGILITY") == 0 || + strcmp(name, "DEXTERITY") == 0 || + strcmp(name, "WISDOM") == 0 || + strcmp(name, "HEALTH") == 0 || + strcmp(name, "MARKSMANSHIP") == 0 || + strcmp(name, "LEADERSHIP") == 0 || + strcmp(name, "MECHANICAL") == 0 || + strcmp(name, "EXPLOSIVES") == 0 || + strcmp(name, "MEDICAL") == 0 || + strcmp(name, "INJURY") == 0 || + strcmp(name, "MORALE") == 0 || + strcmp(name, "FATIGUE") == 0 || + strcmp(name, "LOYALTY_LOCAL") == 0 || + strcmp(name, "LOYALTY_GLOBAL") == 0 || + strcmp(name, "DRUNK") == 0 ) + { + // No data write, simply downgrade the parse stage + pData->curElement = FACILITYTYPE_ASSIGNMENT; + } + + else if(strcmp(name, "ubChance") == 0 ) + { + pData->curElement = FACILITYTYPE_RISK; + pData->curAssignmentData.Risk[pData->curRisk].ubChance = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "bBaseEffect") == 0 ) + { + pData->curElement = FACILITYTYPE_RISK; + pData->curAssignmentData.Risk[pData->curRisk].bBaseEffect = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubRange") == 0 ) + { + pData->curElement = FACILITYTYPE_RISK; + pData->curAssignmentData.Risk[pData->curRisk].ubRange = (UINT8) atol(pData->szCharData); + } + + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + + +BOOLEAN ReadInFacilityTypes(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + facilitytypeParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading FacilityTypes.xml" ); + + // Open merges file + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, facilitytypeStartElementHandle, facilitytypeEndElementHandle); + XML_SetCharacterDataHandler(parser, facilitytypeCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + pData.maxArraySize = MAXITEMS; + pData.curIndex = 0; + + XML_SetUserData(parser, &pData); + + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in FacilityTypes.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + return( TRUE ); +} + diff --git a/Strategic/XML_Roaming.cpp b/Strategic/XML_Roaming.cpp index 3485b26f..ee474f70 100644 --- a/Strategic/XML_Roaming.cpp +++ b/Strategic/XML_Roaming.cpp @@ -18,7 +18,6 @@ extern INT32 iRestrictedSectorArraySize; extern UINT32 gRestrictMilitia[256]; - typedef enum { ROAMING_ELEMENT_NONE = 0, diff --git a/Strategic/XML_SectorNames.cpp b/Strategic/XML_SectorNames.cpp new file mode 100644 index 00000000..9ed5863f --- /dev/null +++ b/Strategic/XML_SectorNames.cpp @@ -0,0 +1,232 @@ +#ifdef PRECOMPILEDHEADERS + #include "Strategic All.h" + #include "XML.h" +#else + #include "builddefines.h" + #include + #include "XML.h" + #include "expat.h" + #include "string.h" + #include "Campaign Types.h" + #include "FileMan.h" + #include "MemMan.h" + #include "Debug Control.h" + #include "mapscreen.h" +#endif + +#define MAX_CHAR_DATA_LENGTH 500 + +// Four different sector names, used at different times. +extern CHAR16 gzSectorNames[256][4][MAX_SECTOR_NAME_LENGTH]; + +typedef enum +{ + SECTORNAME_ELEMENT_NONE = 0, + SECTORNAME_ELEMENT_SECTOR_NAMES, + SECTORNAME_ELEMENT_SECTOR, + SECTORNAME_ELEMENT, +} SECTORNAME_PARSE_STAGE; + +typedef struct +{ + SECTORNAME_PARSE_STAGE curElement; + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + UINT8 sCurSectorX; + UINT8 sCurSectorY; + CHAR16 szCurUnexploredName[MAX_SECTOR_NAME_LENGTH]; + CHAR16 szCurDetailedUnexploredName[MAX_SECTOR_NAME_LENGTH]; + CHAR16 szCurExploredName[MAX_SECTOR_NAME_LENGTH]; + CHAR16 szCurDetailedExploredName[MAX_SECTOR_NAME_LENGTH]; + UINT32 currentDepth; + UINT32 maxReadDepth; +} SectorNameParseData; + + +static void XMLCALL +SectorNameStartElementHandle(void *userData, const XML_Char *name, const char **atts) +{ + SectorNameParseData * pData = (SectorNameParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + + if(strcmp(name, "SECTOR_NAMES") == 0 && pData->curElement == SECTORNAME_ELEMENT_NONE) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR_NAMES; + pData->maxReadDepth++; //we are not skipping this element + // Initiate Array by setting first character to 0. + for (UINT16 x = 0; x < 256; x++) + { + gzSectorNames[x][0][0]=0; + gzSectorNames[x][1][0]=0; + gzSectorNames[x][2][0]=0; + gzSectorNames[x][3][0]=0; + } + } + + else if(strcmp(name, "SECTOR") == 0 && pData->curElement == SECTORNAME_ELEMENT_SECTOR_NAMES) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR; + pData->maxReadDepth++; //we are not skipping this element + } + + else if(pData->curElement == SECTORNAME_ELEMENT_SECTOR && + (strcmp(name, "SectorGrid") == 0 || + strcmp(name, "szUnexploredName") == 0 || + strcmp(name, "szDetailedUnexploredName") == 0 || + strcmp(name, "szExploredName") == 0 || + strcmp(name, "szDetailedExploredName") == 0 )) + { + pData->curElement = SECTORNAME_ELEMENT; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + + + +static void XMLCALL +SectorNameCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + SectorNameParseData * pData = (SectorNameParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth && strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); +} + + +static void XMLCALL +SectorNameEndElementHandle(void *userData, const XML_Char *name) +{ + SectorNameParseData * pData = (SectorNameParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "SECTOR_NAMES") == 0) + { + pData->curElement = SECTORNAME_ELEMENT_NONE; + } + else if(strcmp(name, "SECTOR") == 0) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR_NAMES; + + UINT8 ubSectorId = SECTOR(pData->sCurSectorX, pData->sCurSectorY); + if (ubSectorId >= 0 && ubSectorId < 256) + { + wcscpy(gzSectorNames[ubSectorId][0], pData->szCurUnexploredName); + wcscpy(gzSectorNames[ubSectorId][1], pData->szCurDetailedUnexploredName); + wcscpy(gzSectorNames[ubSectorId][2], pData->szCurExploredName); + wcscpy(gzSectorNames[ubSectorId][3], pData->szCurDetailedExploredName); + } + } + + else if(strcmp(name, "SectorGrid") == 0 ) + { + UINT8 x, y; + pData->curElement = SECTORNAME_ELEMENT_SECTOR; + + y = (UINT8)pData->szCharData[0] & 0x1F; + x = (UINT8)atol(&pData->szCharData[1]); + if ( x > 0 && x <= 16 && y > 0 && y <= 16 ) + { + pData->sCurSectorX = x; + pData->sCurSectorY = y; + } + } + else if(strcmp(name, "szUnexploredName") == 0 ) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->szCurUnexploredName, sizeof(pData->szCurUnexploredName)/sizeof(pData->szCurUnexploredName[0]) ); + pData->szCurUnexploredName[sizeof(pData->szCurUnexploredName)/sizeof(pData->szCurUnexploredName[0]) - 1] = '\0'; + } + + else if(strcmp(name, "szDetailedUnexploredName") == 0 ) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->szCurDetailedUnexploredName, sizeof(pData->szCurDetailedUnexploredName)/sizeof(pData->szCurDetailedUnexploredName[0]) ); + pData->szCurDetailedUnexploredName[sizeof(pData->szCurDetailedUnexploredName)/sizeof(pData->szCurDetailedUnexploredName[0]) - 1] = '\0'; + } + + else if(strcmp(name, "szExploredName") == 0 ) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->szCurExploredName, sizeof(pData->szCurExploredName)/sizeof(pData->szCurExploredName[0]) ); + pData->szCurExploredName[sizeof(pData->szCurExploredName)/sizeof(pData->szCurExploredName[0]) - 1] = '\0'; + } + + else if(strcmp(name, "szDetailedExploredName") == 0 ) + { + pData->curElement = SECTORNAME_ELEMENT_SECTOR; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->szCurDetailedExploredName, sizeof(pData->szCurDetailedExploredName)/sizeof(pData->szCurDetailedExploredName[0]) ); + pData->szCurDetailedExploredName[sizeof(pData->szCurDetailedExploredName)/sizeof(pData->szCurDetailedExploredName[0]) - 1] = '\0'; + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + +BOOLEAN ReadInSectorNames(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + SectorNameParseData pData; + + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, SectorNameStartElementHandle, SectorNameEndElementHandle); + XML_SetCharacterDataHandler(parser, SectorNameCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + XML_SetUserData(parser, &pData); + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + sprintf(errorBuf, "XML Parser Error in SectorNames.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + XML_ParserFree(parser); + + return TRUE; +} diff --git a/Strategic/XML_UniformColors.cpp b/Strategic/XML_UniformColors.cpp new file mode 100644 index 00000000..5544f21c --- /dev/null +++ b/Strategic/XML_UniformColors.cpp @@ -0,0 +1,260 @@ +//////////////////////////////////////////////////////////// +// +// HEADROCK HAM 3.6: +// +// This code reads an XML called "UniformColors.XML", which tells the program which colors to use for +// "standard" uniforms, including randomly-generated enemies and militia (for now). +// +////////////////////////////////////////////////////////////// + + +#ifdef PRECOMPILEDHEADERS + #include "Strategic All.h" + #include "XML.h" +#else + #include "builddefines.h" + #include + #include "XML.h" + #include "expat.h" + #include "string.h" + #include "Campaign Types.h" + #include "FileMan.h" + #include "MemMan.h" + #include "Debug Control.h" + #include "mapscreen.h" +#endif + +#define MAX_CHAR_DATA_LENGTH 500 + +// Error strings for this XML reader +STR16 szUniformErrorStrings[]= +{ + L"ENEMY_ADMIN", + L"ENEMY_TROOP", + L"ENEMY_ELITE", + L"MILITIA_ROOKIE", + L"MILITIA_REGULAR", + L"MILITIA_ELITE", +}; + +// Array to hold uniform data +UNIFORMCOLORS gUniformColors[NUM_UNIFORMS]; + +typedef enum +{ + UNIFORM_ELEMENT_NONE = 0, + UNIFORM_ELEMENT_LIST, + UNIFORM_ELEMENT, + UNIFORM_ELEMENT_DATA, +} UNIFORM_PARSE_STAGE; + +typedef struct +{ + UNIFORM_PARSE_STAGE curElement; + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + UINT8 sCurUniform; + PaletteRepID szCurVest; + PaletteRepID szCurPants; + UINT32 currentDepth; + UINT32 maxReadDepth; +} UniformParseData; + +// Array for flags making sure that we have ALL uniform data defined. +BOOLEAN UniformDataFound[NUM_UNIFORMS]; + +static void XMLCALL +UniformStartElementHandle(void *userData, const XML_Char *name, const char **atts) +{ + UniformParseData * pData = (UniformParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + + if(strcmp(name, "UNIFORMS") == 0 && pData->curElement == UNIFORM_ELEMENT_NONE) + { + pData->curElement = UNIFORM_ELEMENT_LIST; + pData->maxReadDepth++; //we are not skipping this element + for (UINT8 cnt = 0; cnt < NUM_UNIFORMS; cnt++) + { + // reset array + UniformDataFound[cnt] = FALSE; + } + } + + else if(pData->curElement == UNIFORM_ELEMENT_LIST && + (strcmp(name, "ENEMY_ADMIN") == 0 || + strcmp(name, "ENEMY_TROOP") == 0 || + strcmp(name, "ENEMY_ELITE") == 0 || + strcmp(name, "MILITIA_ROOKIE") == 0 || + strcmp(name, "MILITIA_REGULAR") == 0 || + strcmp(name, "MILITIA_ELITE") == 0 ) ) + { + if (strcmp(name, "ENEMY_ADMIN") == 0) + { + pData->sCurUniform = UNIFORM_ENEMY_ADMIN; + } + else if (strcmp(name, "ENEMY_TROOP") == 0) + { + pData->sCurUniform = UNIFORM_ENEMY_TROOP; + } + else if (strcmp(name, "ENEMY_ELITE") == 0) + { + pData->sCurUniform = UNIFORM_ENEMY_ELITE; + } + else if (strcmp(name, "MILITIA_ROOKIE") == 0) + { + pData->sCurUniform = UNIFORM_MILITIA_ROOKIE; + } + else if (strcmp(name, "MILITIA_REGULAR") == 0) + { + pData->sCurUniform = UNIFORM_MILITIA_REGULAR; + } + else if (strcmp(name, "MILITIA_ELITE") == 0) + { + pData->sCurUniform = UNIFORM_MILITIA_ELITE; + } + pData->curElement = UNIFORM_ELEMENT; + pData->maxReadDepth++; //we are not skipping this element + } + + else if(pData->curElement == UNIFORM_ELEMENT && + (strcmp(name, "Vest") == 0 || + strcmp(name, "Pants") == 0 )) + { + pData->curElement = UNIFORM_ELEMENT_DATA; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + + + +static void XMLCALL +UniformCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + UniformParseData * pData = (UniformParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth && strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); +} + + +static void XMLCALL +UniformEndElementHandle(void *userData, const XML_Char *name) +{ + UniformParseData * pData = (UniformParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "UNIFORMS") == 0) + { + // All entries are mandatory. Exception thrown if an entry was not found + for (UINT8 cnt = 0; cnt < NUM_UNIFORMS; cnt++) + { + if (!UniformDataFound[cnt]) + { + CHAR16 sString[256]; + swprintf( sString, L"Error in UniformColors.XML: Uniform color for %s not found or not properly defined!", szUniformErrorStrings[ cnt ] ); + THROWEXCEPTION( sString ); + } + } + + pData->curElement = UNIFORM_ELEMENT_NONE; + } + + else if(strcmp(name, "ENEMY_ADMIN") == 0 || + strcmp(name, "ENEMY_TROOP") == 0 || + strcmp(name, "ENEMY_ELITE") == 0 || + strcmp(name, "MILITIA_ROOKIE") == 0 || + strcmp(name, "MILITIA_REGULAR") == 0 || + strcmp(name, "MILITIA_ELITE") == 0 ) + { + pData->curElement = UNIFORM_ELEMENT_LIST; + + if (pData->sCurUniform >= UNIFORM_ENEMY_ADMIN && pData->sCurUniform < NUM_UNIFORMS ) + { + memcpy( &(gUniformColors[pData->sCurUniform].pants), &(pData->szCurPants), sizeof(PaletteRepID) ); + memcpy( &(gUniformColors[pData->sCurUniform].vest), &(pData->szCurVest), sizeof(PaletteRepID) ); + UniformDataFound[pData->sCurUniform] = TRUE; + } + } + + else if(strcmp(name, "Vest") == 0 ) + { + pData->curElement = UNIFORM_ELEMENT; + + strcpy(pData->szCurVest, pData->szCharData); + } + + else if(strcmp(name, "Pants") == 0 ) + { + pData->curElement = UNIFORM_ELEMENT; + + strcpy(pData->szCurPants, pData->szCharData); + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + +BOOLEAN ReadInUniforms(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + UniformParseData pData; + + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, UniformStartElementHandle, UniformEndElementHandle); + XML_SetCharacterDataHandler(parser, UniformCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + XML_SetUserData(parser, &pData); + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + sprintf(errorBuf, "XML Parser Error in UniformColors.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + XML_ParserFree(parser); + + return TRUE; +} diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index f65657b9..89bf189c 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -111,6 +111,8 @@ #include "Strategic Status.h" #include "Soldier Create.h" #include "Animation Control.h" + // HEADROCK HAM 3.6: Include facilities for assignment display + #include "Facilities.h" #endif #include "connect.h" //hayden @@ -905,6 +907,9 @@ void CreateContractBox( SOLDIERTYPE *pCharacter ); void CreateAssignmentsBox( void ); void CreateTrainingBox( void ); void CreateMercRemoveAssignBox( void ); +// HEADROCK HAM 3.6: Facility box, Sub-menu +void CreateFacilityBox( void ); +void CreateFacilityAssignmentBox( void ); void DetermineWhichAssignmentMenusCanBeShown( void ); void DetermineWhichMilitiaControlMenusCanBeShown( void ); //lal @@ -2837,16 +2842,29 @@ void DrawCharacterInfo(INT16 sCharNumber) // second assignment line + // HEADROCK HAM 3.6: Operating a facility? + if (pSoldier->sFacilityTypeOperated != -1) + { + UINT8 ubFacilityType = (UINT8)pSoldier->sFacilityTypeOperated; + // Print sector ID string + GetShortSectorString( pSoldier->sSectorX, pSoldier->sSectorY, sString ); + wcscat( sString, L": " ); + wcscat( sString, gFacilityTypes[ubFacilityType].szFacilityShortName ); + //wcscpy(sString, gFacilityTypes[ pSoldier->sFacilityTypeOperated ].szFacilityName); + } + // train self / teammate / by other ? - if( ( pSoldier->bAssignment == TRAIN_SELF ) || ( pSoldier->bAssignment == TRAIN_TEAMMATE ) || ( pSoldier->bAssignment == TRAIN_BY_OTHER ) ) + else if( ( pSoldier->bAssignment == TRAIN_SELF ) || ( pSoldier->bAssignment == TRAIN_TEAMMATE ) || ( pSoldier->bAssignment == TRAIN_BY_OTHER ) ) { wcscpy(sString, pAttributeMenuStrings[pSoldier->bTrainStat]); } // train town? - else if( pSoldier->bAssignment == TRAIN_TOWN ) + // HEADROCK HAM 3.6: Draw for Mobile Trainers as well. + else if( pSoldier->bAssignment == TRAIN_TOWN || pSoldier->bAssignment == TRAIN_MOBILE ) { wcscpy(sString,pTownNames[GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY ) ] ); } + // repairing? else if( pSoldier->bAssignment == REPAIR ) { @@ -3127,6 +3145,136 @@ void DisplayCharacterInfo( void ) // set font buffer SetFontDestBuffer( guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); + // HEADROCK HAM 3.6: "progress" bars showing how near the character is to "leveling up" in any stat. The bar + // is displayed behind the current stat value, as see on the character's info panel. + // This section draws STRATEGIC info pages. Another section is in Interface Panels.cpp and draws TACTICAL info pages. + // The feature is toggled by Options-Menu switch, and its color is determined in the INI files. + if ( gGameSettings.fOptions[TOPTION_STAT_PROGRESS_BARS] ) + { + SOLDIERTYPE *pSoldier = MercPtrs[ gCharactersList[bSelectedInfoChar].usSolID ]; + + UINT8 *pDestBuf; + UINT32 uiDestPitchBYTES = 0; + SGPRect ClipRect; + UINT8 ubBarWidth = 0; + UINT16 usColor = Get16BPPColor( FROMRGB( gGameExternalOptions.ubStatProgressBarsRed, gGameExternalOptions.ubStatProgressBarsGreen, gGameExternalOptions.ubStatProgressBarsBlue ) ); + //pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES ); + pDestBuf = LockVideoSurface( guiSAVEBUFFER, &uiDestPitchBYTES ); + + // AGI + if (gMercProfiles[ pSoldier->ubProfile ].sAgilityGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sAgilityGain+1)) / SubpointsPerPoint(AGILAMT,0); + ClipRect.iTop = (AGL_Y-1); + ClipRect.iBottom = (AGL_Y-1) + STAT_HEI; + ClipRect.iLeft = AGL_X; + ClipRect.iRight = AGL_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // DEX + if (gMercProfiles[ pSoldier->ubProfile ].sDexterityGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sDexterityGain+1)) / SubpointsPerPoint(DEXTAMT,0); + ClipRect.iTop = (DEX_Y-1); + ClipRect.iBottom = (DEX_Y-1) + STAT_HEI; + ClipRect.iLeft = DEX_X; + ClipRect.iRight = DEX_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // STR + if (gMercProfiles[ pSoldier->ubProfile ].sStrengthGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sStrengthGain+1)) / SubpointsPerPoint(STRAMT,0); + ClipRect.iTop = (STR_Y-1); + ClipRect.iBottom = (STR_Y-1) + STAT_HEI; + ClipRect.iLeft = STR_X; + ClipRect.iRight = STR_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // WIS + if (gMercProfiles[ pSoldier->ubProfile ].sWisdomGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sWisdomGain+1)) / SubpointsPerPoint(WISDOMAMT,0); + ClipRect.iTop = (WIS_Y-1); + ClipRect.iBottom = (WIS_Y-1) + STAT_HEI; + ClipRect.iLeft = WIS_X; + ClipRect.iRight = WIS_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // MRK + if (gMercProfiles[ pSoldier->ubProfile ].sMarksmanshipGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sMarksmanshipGain+1)) / SubpointsPerPoint(MARKAMT,0); + ClipRect.iTop = (MRK_Y-1); + ClipRect.iBottom = (MRK_Y-1) + STAT_HEI; + ClipRect.iLeft = MRK_X; + ClipRect.iRight = MRK_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // LDR + if (gMercProfiles[ pSoldier->ubProfile ].sLeadershipGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sLeadershipGain+1)) / SubpointsPerPoint(LDRAMT,0); + ClipRect.iTop = (LDR_Y-1); + ClipRect.iBottom = (LDR_Y-1) + STAT_HEI; + ClipRect.iLeft = LDR_X; + ClipRect.iRight = LDR_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // MECH + if (gMercProfiles[ pSoldier->ubProfile ].sMechanicGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sMechanicGain+1)) / SubpointsPerPoint(MECHANAMT,0); + ClipRect.iTop = (MEC_Y-1); + ClipRect.iBottom = (MEC_Y-1) + STAT_HEI; + ClipRect.iLeft = MEC_X; + ClipRect.iRight = MEC_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // EXPLO + if (gMercProfiles[ pSoldier->ubProfile ].sExplosivesGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sExplosivesGain+1)) / SubpointsPerPoint(EXPLODEAMT,0); + ClipRect.iTop = (EXP_Y-1); + ClipRect.iBottom = (EXP_Y-1) + STAT_HEI; + ClipRect.iLeft = EXP_X; + ClipRect.iRight = EXP_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // MED + if (gMercProfiles[ pSoldier->ubProfile ].sMedicalGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sMedicalGain+1)) / SubpointsPerPoint(MEDICALAMT,0); + ClipRect.iTop = (MED_Y-1); + ClipRect.iBottom = (MED_Y-1) + STAT_HEI; + ClipRect.iLeft = MED_X; + ClipRect.iRight = MED_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // EXPLEVEL + if (gMercProfiles[ pSoldier->ubProfile ].sExpLevelGain) + { + ubBarWidth = (STAT_WID * (gMercProfiles[ pSoldier->ubProfile ].sExpLevelGain+1)) / SubpointsPerPoint(EXPERAMT, pSoldier->stats.bExpLevel); + ClipRect.iTop = (LVL_Y-1); + ClipRect.iBottom = (LVL_Y-1) + STAT_HEI; + ClipRect.iLeft = LVL_X; + ClipRect.iRight = LVL_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + UnLockVideoSurface( guiSAVEBUFFER ); + } + + // draw character info and face DrawCharacterInfo( bSelectedInfoChar ); @@ -4428,12 +4576,16 @@ UINT32 MapScreenHandle(void) InitPreviousPaths(); + // HEADROCK HAM 3.6: Init coordinates for new variable-sized message window + InitMapScreenInterfaceBottomCoords(); + // if arrival sector is invalid, reset to A9 if ( ( gsMercArriveSectorX < 1 ) || ( gsMercArriveSectorY < 1 ) || ( gsMercArriveSectorX > 16 ) || ( gsMercArriveSectorY > 16 ) ) { - gsMercArriveSectorX = 9; - gsMercArriveSectorY = 1; + // HEADROCK HAM 3.5: Externalized coordinates + gsMercArriveSectorX = gGameExternalOptions.ubDefaultArrivalSectorX; + gsMercArriveSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; } gfInConfirmMapMoveMode = FALSE; @@ -4702,7 +4854,8 @@ UINT32 MapScreenHandle(void) if( AnyMercsHired( ) == FALSE ) { // select starting sector (A9 - Omerta) - ChangeSelectedMapSector( startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Externalized! + ChangeSelectedMapSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); } else if( ( gWorldSectorX > 0 ) && ( gWorldSectorY > 0 ) && ( gbWorldSectorZ != -1 ) ) { @@ -4714,7 +4867,8 @@ UINT32 MapScreenHandle(void) // only select A9 - Omerta IF there is no current selection, otherwise leave it as is if ( ( sSelMapX == 0 ) || ( sSelMapY == 0 ) || ( iCurrentMapSectorZ == -1 ) ) { - ChangeSelectedMapSector( startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Externalized! + ChangeSelectedMapSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); } } @@ -5261,7 +5415,8 @@ UINT32 MapScreenHandle(void) // handle display of inventory pop up - HandleDisplayOfItemPopUpForSector( startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Externalize! + HandleDisplayOfItemPopUpForSector( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); // Display Framerate DisplayFrameRate( ); @@ -5311,7 +5466,9 @@ UINT32 MapScreenHandle(void) - + fCharacterInfoPanelDirty=TRUE; + fMapPanelDirty = TRUE; + fMapScreenBottomDirty = TRUE; fTeamPanelDirty = TRUE; fEndShowInventoryFlag = FALSE; } @@ -5702,6 +5859,8 @@ void DrawAssignment(INT16 sCharNumber, INT16 sRowIndex, INT32 iFont) FindFontCenterCoordinates((short)ASSIGN_X + 1, (short)(usVehicleY+(sRowIndex*Y_SIZE)), (short)ASSIGN_WIDTH, (short)Y_SIZE, sString, (long)iFont, &usX, &usY); } + SOLDIERTYPE *pSoldier = MercPtrs[ gCharactersList[ sCharNumber ].usSolID ]; + if( fFlashAssignDone == TRUE ) { if( Menptr[gCharactersList[sCharNumber].usSolID].flags.fDoneAssignmentAndNothingToDoFlag ) @@ -5710,6 +5869,97 @@ void DrawAssignment(INT16 sCharNumber, INT16 sRowIndex, INT32 iFont) } } + if ( gGameSettings.fOptions[TOPTION_STAT_PROGRESS_BARS] && + (pSoldier->bAssignment >= TRAIN_SELF || + pSoldier->bAssignment <= TRAIN_BY_OTHER ) && + pSoldier->bAssignment != TRAIN_TEAMMATE ) + { + // HEADROCK HAM 3.6: Draw militia training progress bar + UINT32 uiDestBuffer; + UINT8 *pDestBuf; + UINT32 uiDestPitchBYTES = 0; + SGPRect ClipRect; + UINT8 ubBarWidth; + UINT16 usColor = Get16BPPColor( FROMRGB( gGameExternalOptions.ubStatProgressBarsRed, gGameExternalOptions.ubStatProgressBarsGreen, gGameExternalOptions.ubStatProgressBarsBlue ) ); + + if( ( fShowAssignmentMenu == TRUE ) && ( fTeamPanelDirty == FALSE ) ) + { + uiDestBuffer = FRAME_BUFFER; + } + else + { + uiDestBuffer = guiSAVEBUFFER; + } + pDestBuf = LockVideoSurface( uiDestBuffer, &uiDestPitchBYTES ); + + UINT8 ubProgress; + UINT16 usMaxProgress; + UINT8 sMapX = (UINT8)pSoldier->sSectorX; + UINT8 sMapY = (UINT8)pSoldier->sSectorY; + if ( pSoldier->bAssignment == TRAIN_TOWN ) + { + ubProgress = SectorInfo[SECTOR(sMapX, sMapY)].ubMilitiaTrainingPercentDone; + usMaxProgress = 100; + } + else if ( pSoldier->bAssignment == TRAIN_MOBILE ) + { + ubProgress = SectorInfo[SECTOR(sMapX, sMapY)].ubMobileMilitiaTrainingPercentDone; + usMaxProgress = 100; + } + else if ( pSoldier->bAssignment == TRAIN_SELF || + pSoldier->bAssignment == TRAIN_BY_OTHER ) + { + switch (pSoldier->bTrainStat) + { + case STRENGTH: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sStrengthGain+1; + usMaxProgress = gGameExternalOptions.usStrengthSubpointsToImprove; + break; + case AGILITY: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sAgilityGain+1; + usMaxProgress = gGameExternalOptions.usAgilitySubpointsToImprove; + break; + case DEXTERITY: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sDexterityGain+1; + usMaxProgress = gGameExternalOptions.usDexteritySubpointsToImprove; + break; + case HEALTH: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sLifeGain+1; + usMaxProgress = gGameExternalOptions.usHealthSubpointsToImprove; + break; + case MARKSMANSHIP: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sMarksmanshipGain+1; + usMaxProgress = gGameExternalOptions.usMarksmanshipSubpointsToImprove; + break; + case LEADERSHIP: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sLeadershipGain+1; + usMaxProgress = gGameExternalOptions.usLeadershipSubpointsToImprove; + break; + case MECHANICAL: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sMechanicGain+1; + usMaxProgress = gGameExternalOptions.usMechanicalSubpointsToImprove; + break; + case MEDICAL: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sMedicalGain+1; + usMaxProgress = gGameExternalOptions.usMedicalSubpointsToImprove; + break; + case EXPLOSIVE_ASSIGN: + ubProgress = gMercProfiles[ pSoldier->ubProfile ].sExplosivesGain+1; + usMaxProgress = gGameExternalOptions.usExplosivesSubpointsToImprove; + break; + } + } + + ubBarWidth = (ASSIGN_WIDTH * ubProgress) / usMaxProgress; + ClipRect.iTop = (usY+(Y_OFFSET*sRowIndex+1))-1; + ClipRect.iBottom = ClipRect.iTop + Y_SIZE; + ClipRect.iLeft = ASSIGN_X+2; + ClipRect.iRight = ASSIGN_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + + UnLockVideoSurface( uiDestBuffer ); + } + //RestoreExternBackgroundRect(ASSIGN_X-2, ((UINT16)(usY+(Y_OFFSET*sRowIndex+1))), ASSIGN_WIDTH+2, Y_SIZE); DrawString(sString, (UINT16)usX, ((UINT16)(usY+(Y_OFFSET*sRowIndex+1))), iFont); } @@ -6438,6 +6688,12 @@ void GetMapKeyboardInput( UINT32 *puiNewEvent ) fShowAttributeMenu = FALSE; fMapPanelDirty = TRUE; } + // stop showing current assignment box + if( fShowFacilityAssignmentMenu == TRUE ) + { + fShowFacilityAssignmentMenu = FALSE; + fMapPanelDirty = TRUE; + } else if( fShowTrainingMenu == TRUE ) { fShowTrainingMenu = FALSE; @@ -6450,6 +6706,11 @@ void GetMapKeyboardInput( UINT32 *puiNewEvent ) { fShowRepairMenu = FALSE; } + // HEADROCK HAM 3.6: Facility Menu + else if( fShowFacilityMenu == TRUE ) + { + fShowFacilityMenu = FALSE; + } else { fShowAssignmentMenu = FALSE; @@ -10695,6 +10956,12 @@ void RenderMapRegionBackground( void ) ForceUpDateOfBox( ghAttributeBox ); } + // HEADROCK HAM 3.6: Force re-render due to Facility box + if ( ghFacilityAssignmentBox != 1 ) + { + ForceUpDateOfBox( ghFacilityAssignmentBox ); + } + if ( ghTownMineBox != - 1 ) { // force update of town mine info boxes @@ -14970,13 +15237,18 @@ void HandlePostAutoresolveMessages() void GetMapscreenMercAssignmentString( SOLDIERTYPE *pSoldier, CHAR16 sString[] ) { - if( pSoldier->bAssignment != VEHICLE ) + if ( pSoldier->bAssignment == VEHICLE ) { - wcscpy(sString, pAssignmentStrings[ pSoldier->bAssignment ] ); + wcscpy( sString, pShortVehicleStrings[ pVehicleList[ pSoldier->iVehicleId ].ubVehicleType ] ); } else { - wcscpy( sString, pShortVehicleStrings[ pVehicleList[ pSoldier->iVehicleId ].ubVehicleType ] ); + wcscpy(sString, pAssignmentStrings[ pSoldier->bAssignment ] ); + } + // If soldier is working at a facility, add an asterisk. + if (pSoldier->sFacilityTypeOperated != -1) + { + wcscat(sString, L"*"); } } @@ -15433,6 +15705,7 @@ void RequestToggleMercInventoryPanel( void ) else { // Headrock. New line forces InvBottom rerender when the inventory is opened and closed. + fEndShowInventoryFlag = TRUE; RenderMapScreenInterfaceBottom ( TRUE ); SetRegionFastHelpText( &gCharInfoHandRegion, pMiscMapScreenMouseRegionHelpText[ 0 ] ); } @@ -15549,3 +15822,48 @@ void MapscreenMarkButtonsDirty() } } } + +// HEADROCK HAM 3.6: Get a total of all merc salaries. Used for the new Daily Expenses display. +INT32 GetTotalContractExpenses ( void ) +{ + if (gGameExternalOptions.ubIncludeContractsInExpenses == 0) + { + // Contract costs are excluded. + return (0); + } + + UINT8 ubCounter = 0; + SOLDIERTYPE *pSoldier; + INT32 iTotalCost = 0; + + while(gCharactersList[ubCounter].fValid) + { + pSoldier = MercPtrs[ gCharactersList[ ubCounter ].usSolID ]; + // salary + if( pSoldier->ubWhatKindOfMercAmI == MERC_TYPE__AIM_MERC ) + { + if (gGameExternalOptions.ubIncludeContractsInExpenses == 2) + { + // daily rate + if( pSoldier->bTypeOfLastContract == CONTRACT_EXTEND_2_WEEK ) + { + iTotalCost += ( gMercProfiles[ pSoldier->ubProfile ].uiBiWeeklySalary / 14 ); + } + if( pSoldier->bTypeOfLastContract == CONTRACT_EXTEND_1_WEEK ) + { + iTotalCost += ( gMercProfiles[ pSoldier->ubProfile ].uiWeeklySalary / 7 ); + } + else + { + iTotalCost += gMercProfiles[ pSoldier->ubProfile ].sSalary; + } + } + } + else + { + iTotalCost += gMercProfiles[ pSoldier->ubProfile ].sSalary; + } + ubCounter++; + } + return (iTotalCost); +} \ No newline at end of file diff --git a/Strategic/mapscreen.h b/Strategic/mapscreen.h index ab4e56d9..90cb97f8 100644 --- a/Strategic/mapscreen.h +++ b/Strategic/mapscreen.h @@ -9,6 +9,7 @@ #define MAP_WORLD_Y 18 #define MAX_TOWN_NAME_LENGHT 32 +#define MAX_SECTOR_NAME_LENGTH 128 #define MAX_TOWNS 50 //13 //50 #define MAX_TOWN_SECTORS 260 //40 //260 @@ -93,4 +94,7 @@ BOOLEAN InitializeInvPanelCoordsOld( ); BOOLEAN InitializeInvPanelCoordsNew( ); BOOLEAN InitializeInvPanelCoordsVehicle( ); +// HEADROCK HAM 3.6: Calculate daily cost for all mercs who have one. +INT32 GetTotalContractExpenses ( void ); + #endif \ No newline at end of file diff --git a/Strategic/strategicmap.cpp b/Strategic/strategicmap.cpp index 251c7ba1..c0abd4b4 100644 --- a/Strategic/strategicmap.cpp +++ b/Strategic/strategicmap.cpp @@ -296,6 +296,9 @@ STR8 pHortStrings[]={ "17", }; +// HEADROCK HAM 3.6: Array to hold user-defined sector names +CHAR16 gzSectorNames[256][4][MAX_SECTOR_NAME_LENGTH]; + BOOLEAN ReadInStrategicMapSectorTownNames(STR fileName, BOOLEAN localizedVersion); void DoneFadeOutAdjacentSector( void ); @@ -1616,8 +1619,9 @@ void InitializeSAMSites( void ) { UINT32 cnt; // move the landing zone over to Omerta - gsMercArriveSectorX = 9; - gsMercArriveSectorY = 1; + // HEADROCK HAM 3.5: Externalized coordinates + gsMercArriveSectorX = gGameExternalOptions.ubDefaultArrivalSectorX; + gsMercArriveSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; // all SAM sites start game in perfect working condition for ( cnt = 0; cnt < NUMBER_OF_SAMS; cnt++ ) @@ -3009,109 +3013,179 @@ void GetSectorIDString( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ , STR16 zS } else { + bTownNameID = StrategicMap[ CALCULATE_STRATEGIC_INDEX( sSectorX, sSectorY ) ].bNameId; ubSectorID = (UINT8)SECTOR( sSectorX, sSectorY ); + AssertGE(ubSectorID, 0); + AssertLT(ubSectorID,256); pSector = &SectorInfo[ ubSectorID ]; ubLandType = pSector->ubTraversability[ 4 ]; swprintf( zString, L"%c%d: ", 'A' + sSectorY - 1, sSectorX ); - if ( bTownNameID == BLANK_SECTOR ) + //////////////////////////////////// + // Read and verify XML sector names + BOOLEAN fSectorHasXMLNames = TRUE; + CHAR16 zUnexplored[MAX_SECTOR_NAME_LENGTH]; + CHAR16 zDetailedUnexplored[MAX_SECTOR_NAME_LENGTH]; + CHAR16 zExplored[MAX_SECTOR_NAME_LENGTH]; + CHAR16 zDetailedExplored[MAX_SECTOR_NAME_LENGTH]; + + wcscpy( zUnexplored, gzSectorNames[ ubSectorID ][0] ); + wcscpy( zDetailedUnexplored, gzSectorNames[ ubSectorID ][1] ); + wcscpy( zExplored, gzSectorNames[ ubSectorID ][2] ); + wcscpy( zDetailedExplored, gzSectorNames[ ubSectorID ][3] ); + + if (zUnexplored[0] == 0 || zDetailedUnexplored[0] == 0 || zExplored[0] == 0 || zDetailedExplored[0] == 0) { - // OK, build string id like J11 - // are we dealing with the unfound towns? - switch( ubSectorID ) + fSectorHasXMLNames = FALSE; + } + + if (fSectorHasXMLNames) + { + // HEADROCK HAM 3.6: The program can now read custom names from XML for all above-ground sectors. + // In the event that a specific name or set of names is missing, the program generates a default + // name as it always has. + // I've also updated the SAM Site sectors to rely on SamSite.XML data. + + BOOLEAN fVisited = (SectorInfo[ ubSectorID ].uiFlags & SF_ALREADY_VISITED); + BOOLEAN fSAMSiteKnown = FALSE; + + // Test for known SAM Site at this location + for (UINT16 x=0; x < MAX_NUMBER_OF_SAMS; x++) { - case SEC_D2: //Chitzena SAM - if( !fSamSiteFound[ SAM_SITE_ONE ] ) - wcscat( zString, pLandTypeStrings[ TROPICS ] ); - else if( fDetailed ) - wcscat( zString, pLandTypeStrings[ TROPICS_SAM_SITE ] ); - else - wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); - break; - case SEC_D15: //Drassen SAM - if( !fSamSiteFound[ SAM_SITE_TWO ] ) - wcscat( zString, pLandTypeStrings[ SPARSE ] ); - else if( fDetailed ) - wcscat( zString, pLandTypeStrings[ SPARSE_SAM_SITE ] ); - else - wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); - break; - case SEC_I8: //Cambria SAM - if( !fSamSiteFound[ SAM_SITE_THREE ] ) - wcscat( zString, pLandTypeStrings[ SAND ] ); - else if( fDetailed ) - wcscat( zString, pLandTypeStrings[ SAND_SAM_SITE ] ); - else - wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); - break; - default: - wcscat( zString, pLandTypeStrings[ ubLandType ] ); - break; + if ( pSamList[x] == ubSectorID ) + { + if ( fSamSiteFound[ x ] ) + { + fSAMSiteKnown = TRUE; + } + } + } + + if (fVisited || fSAMSiteKnown) + { + if (fDetailed) + { + wcscat( zString, zDetailedExplored ); + } + else + { + wcscat( zString, zExplored); + } + } + else + { + if (fDetailed) + { + wcscat( zString, zDetailedUnexplored); + } + else + { + wcscat( zString, zUnexplored ); + } } } else { - switch( ubSectorID ) + // Default JA2 Name selection + if ( bTownNameID == BLANK_SECTOR ) { - case SEC_B13: - if( fDetailed ) - wcscat( zString, pLandTypeStrings[ DRASSEN_AIRPORT_SITE ] ); - else - wcscat( zString, pTownNames[ DRASSEN ] ); - break; - case SEC_F8: - if( fDetailed ) - wcscat( zString, pLandTypeStrings[ CAMBRIA_HOSPITAL_SITE ] ); - else - wcscat( zString, pTownNames[ CAMBRIA ] ); - break; - case SEC_J9: //Tixa - if( !fFoundTixa ) - wcscat( zString, pLandTypeStrings[ SAND ] ); - else - wcscat( zString, pTownNames[ TIXA ] ); - break; - case SEC_K4: //Orta - if( !fFoundOrta ) - wcscat( zString, pLandTypeStrings[ SWAMP ] ); - else - wcscat( zString, pTownNames[ ORTA ] ); - break; - case SEC_N3: - if( fDetailed ) - wcscat( zString, pLandTypeStrings[ MEDUNA_AIRPORT_SITE ] ); - else - wcscat( zString, pTownNames[ MEDUNA ] ); - break; - default: - if( ubSectorID == SEC_N4 && fSamSiteFound[ SAM_SITE_FOUR ] ) - { //Meduna's SAM site - if( fDetailed ) - wcscat( zString, pLandTypeStrings[ MEDUNA_SAM_SITE ] ); + // OK, build string id like J11 + // are we dealing with the unfound towns? + switch( ubSectorID ) + { + case SEC_D2: //Chitzena SAM + if( !fSamSiteFound[ SAM_SITE_ONE ] ) + wcscat( zString, pLandTypeStrings[ TROPICS ] ); + else if( fDetailed ) + wcscat( zString, pLandTypeStrings[ TROPICS_SAM_SITE ] ); else wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); - } - else - { //All other towns that are known since beginning of the game. - wcscat( zString, pTownNames[ bTownNameID ] ); + break; + case SEC_D15: //Drassen SAM + if( !fSamSiteFound[ SAM_SITE_TWO ] ) + wcscat( zString, pLandTypeStrings[ SPARSE ] ); + else if( fDetailed ) + wcscat( zString, pLandTypeStrings[ SPARSE_SAM_SITE ] ); + else + wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); + break; + case SEC_I8: //Cambria SAM + if( !fSamSiteFound[ SAM_SITE_THREE ] ) + wcscat( zString, pLandTypeStrings[ SAND ] ); + else if( fDetailed ) + wcscat( zString, pLandTypeStrings[ SAND_SAM_SITE ] ); + else + wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); + break; + default: + wcscat( zString, pLandTypeStrings[ ubLandType ] ); + break; + } + } + else + { + switch( ubSectorID ) + { + case SEC_B13: if( fDetailed ) - { - switch( ubSectorID ) - { //Append the word, "mine" for town sectors containing a mine. - case SEC_B2: - case SEC_D4: - case SEC_D13: - case SEC_H3: - case SEC_H8: - case SEC_I14: - wcscat( zString, L" " ); //space - wcscat( zString, pwMineStrings[ 0 ] ); //then "Mine" - break; + wcscat( zString, pLandTypeStrings[ DRASSEN_AIRPORT_SITE ] ); + else + wcscat( zString, pTownNames[ DRASSEN ] ); + break; + case SEC_F8: + if( fDetailed ) + wcscat( zString, pLandTypeStrings[ CAMBRIA_HOSPITAL_SITE ] ); + else + wcscat( zString, pTownNames[ CAMBRIA ] ); + break; + case SEC_J9: //Tixa + if( !fFoundTixa ) + wcscat( zString, pLandTypeStrings[ SAND ] ); + else + wcscat( zString, pTownNames[ TIXA ] ); + break; + case SEC_K4: //Orta + if( !fFoundOrta ) + wcscat( zString, pLandTypeStrings[ SWAMP ] ); + else + wcscat( zString, pTownNames[ ORTA ] ); + break; + case SEC_N3: + if( fDetailed ) + wcscat( zString, pLandTypeStrings[ MEDUNA_AIRPORT_SITE ] ); + else + wcscat( zString, pTownNames[ MEDUNA ] ); + break; + default: + if( ubSectorID == SEC_N4 && fSamSiteFound[ SAM_SITE_FOUR ] ) + { //Meduna's SAM site + if( fDetailed ) + wcscat( zString, pLandTypeStrings[ MEDUNA_SAM_SITE ] ); + else + wcscat( zString, pLandTypeStrings[ SAM_SITE ] ); + } + else + { //All other towns that are known since beginning of the game. + wcscat( zString, pTownNames[ bTownNameID ] ); + if( fDetailed ) + { + switch( ubSectorID ) + { //Append the word, "mine" for town sectors containing a mine. + case SEC_B2: + case SEC_D4: + case SEC_D13: + case SEC_H3: + case SEC_H8: + case SEC_I14: + wcscat( zString, L" " ); //space + wcscat( zString, pwMineStrings[ 0 ] ); //then "Mine" + break; + } } } - } - break; + break; + } } } } @@ -4605,8 +4679,26 @@ void UpdateAirspaceControl( void ) GetSectorIDString( gsMercArriveSectorX, gsMercArriveSectorY, 0, sMsgSubString1, FALSE ); // move the landing zone over to Omerta - gsMercArriveSectorX = 9; - gsMercArriveSectorY = 1; + // HEADROCK HAM 3.5: Externalized coordinates + if ( StrategicMap[ CALCULATE_STRATEGIC_INDEX( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY ) ].fEnemyAirControlled == FALSE ) + { + gsMercArriveSectorX = gGameExternalOptions.ubDefaultArrivalSectorX; + gsMercArriveSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; + } + else + { + for (UINT8 ubSectorX = 1; ubSectorX <= 16; ubSectorX++) + { + for (UINT8 ubSectorY = 1; ubSectorY <=16; ubSectorY++) + { + if ( StrategicMap[ CALCULATE_STRATEGIC_INDEX( ubSectorX, ubSectorY ) ].fEnemyAirControlled == FALSE ) + { + gsMercArriveSectorX = ubSectorX; + gsMercArriveSectorY = ubSectorY; + } + } + } + } // get the name of the new sector GetSectorIDString( gsMercArriveSectorX, gsMercArriveSectorY, 0, sMsgSubString2, FALSE ); @@ -4615,7 +4707,11 @@ void UpdateAirspaceControl( void ) swprintf( sMsgString, pBullseyeStrings[ 4 ], sMsgSubString1, sMsgSubString2 ); // confirm the change with overlay message - DoScreenIndependantMessageBox( sMsgString, MSG_BOX_FLAG_OK, NULL ); + // HEADROCK HAM 3.5: If we do this during SaveLoad, the message box prevents loading the game at all! + if (guiCurrentScreen != SAVE_LOAD_SCREEN) + { + DoScreenIndependantMessageBox( sMsgString, MSG_BOX_FLAG_OK, NULL ); + } // update position of bullseye fMapPanelDirty = TRUE; @@ -4627,6 +4723,7 @@ void UpdateAirspaceControl( void ) // ARM: airspace control now affects refueling site availability, so update that too with every change! UpdateRefuelSiteAvailability( ); + } @@ -5524,6 +5621,12 @@ void HandleSlayDailyEvent( void ) BOOLEAN IsSectorDesert( INT16 sSectorX, INT16 sSectorY ) { + // HEADROCK HAM 3.5: Fix for assertion errors associated with rain checks. + if (sSectorX <= 0 || sSectorY <=0 || sSectorX > 16 || sSectorY > 16) + { + return( FALSE ); + } + if ( SectorInfo[ SECTOR(sSectorX, sSectorY) ].ubTraversability[ THROUGH_STRATEGIC_MOVE ] == SAND ) { // desert @@ -5669,7 +5772,7 @@ BOOLEAN CheckAndHandleUnloadingOfCurrentWorld() return FALSE; } - if( gTacticalStatus.fDidGameJustStart && gWorldSectorX == 9 && gWorldSectorY == 1 && !gbWorldSectorZ ) + if( gTacticalStatus.fDidGameJustStart && gWorldSectorX == gGameExternalOptions.ubDefaultArrivalSectorX && gWorldSectorY == gGameExternalOptions.ubDefaultArrivalSectorY && !gbWorldSectorZ ) { return FALSE; } diff --git a/Tactical/Boxing.cpp b/Tactical/Boxing.cpp index e8dd247e..b4959690 100644 --- a/Tactical/Boxing.cpp +++ b/Tactical/Boxing.cpp @@ -82,14 +82,13 @@ void ExitBoxing( void ) CancelAIAction( pSoldier, TRUE ); pSoldier->aiData.bAlertStatus = STATUS_GREEN; pSoldier->aiData.bUnderFire = 0; - // HEADROCK HAM 3.6: Make sure all boxers' APs have been reset to a reasonable number. Otherwise, - // the AI combatant may fail several conditions in subsequent functions, and fail to leave the ring - // as a result. - if (pSoldier->bActionPoints < (APBPConstants[AP_MAXIMUM]*6)/10) - { - pSoldier->bActionPoints = (APBPConstants[AP_MAXIMUM]*6)/10; - } + // the AI combatant may fail several conditions in subsequent functions, and fail to leave the ring + // as a result. + if (pSoldier->bActionPoints < (APBPConstants[AP_MAXIMUM]*6)/10) + { + pSoldier->bActionPoints = (APBPConstants[AP_MAXIMUM]*6)/10; + } // if necessary, revive boxer so he can leave ring if (pSoldier->stats.bLife > 0 && (pSoldier->stats.bLife < OKLIFE || pSoldier->bBreath < OKBREATH ) ) @@ -152,6 +151,7 @@ void BoxingPlayerDisqualified( SOLDIERTYPE * pOffender, INT8 bReason ) void TriggerEndOfBoxingRecord( SOLDIERTYPE * pSoldier ) { + // This trigger runs once for each boxer, so only when the SECOND boxer gets out will this function run in its entirely. // unlock UI guiPendingOverrideEvent = LU_ENDUILOCK; diff --git a/Tactical/Campaign.cpp b/Tactical/Campaign.cpp index 83224bfd..244690db 100644 --- a/Tactical/Campaign.cpp +++ b/Tactical/Campaign.cpp @@ -1080,17 +1080,17 @@ 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; + UINT16 HEALTH_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usHealthSubpointsToImprove; + UINT16 STRENGTH_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usStrengthSubpointsToImprove; + UINT16 DEXTERITY_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usDexteritySubpointsToImprove; + UINT16 AGILITY_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usAgilitySubpointsToImprove; + UINT16 WISDOM_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usWisdomSubpointsToImprove; + UINT16 MARKSMANSHIP_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usMarksmanshipSubpointsToImprove; + UINT16 MEDICAL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usMedicalSubpointsToImprove; + UINT16 MECHANICAL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usMechanicalSubpointsToImprove; + UINT16 LEADERSHIP_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usLeadershipSubpointsToImprove; + UINT16 EXPLOSIVES_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usExplosivesSubpointsToImprove; + UINT16 LEVEL_SUBPOINTS_TO_IMPROVE = gGameExternalOptions.usLevelSubpointsToImprove; // figure out how many subpoints this type of stat needs to change switch (ubStat) { @@ -1299,16 +1299,47 @@ UINT8 CurrentPlayerProgressPercentage(void) { UINT32 uiCurrentIncome; UINT32 uiPossibleIncome; - UINT8 ubCurrentProgress; - UINT8 ubKillsPerPoint; - UINT16 usKillsProgress; - UINT16 usControlProgress; - UINT16 usVisitProgress; + // HEADROCK HAM 3: Changed to UINT16 to avoid overflow + UINT16 usCurrentProgress; + UINT16 ubKillsPerPoint; + UINT16 usKillsProgress; + UINT16 usControlProgress; + UINT16 usVisitProgress; + // HEADROCK HAM 3: Added a separate variable for Income Progress, + // to enable comparing the results from each progress aspect SEPARATELY. + UINT16 usIncomeProgress; + // HEADROCK HAM 3: And another variable to contain the highest result so far. + UINT16 usHighestProgress; + // HEADROCK HAM 3: Four variables to hold the maximum attainable progress from each aspect. + UINT16 usMaxKillsProgress; + UINT16 usMaxIncomeProgress; + UINT16 usMaxControlProgress; + UINT16 usMaxVisitProgress; if( gfEditMode ) return 0; + // HEADROCK HAM 3: If the alternate progress calculation is used, all four INI settings should be set to 100, + // otherwise progress cannot ever reach 100... + if (gGameExternalOptions.fAlternateProgressCalculation) + { + usMaxKillsProgress = 100; + usMaxIncomeProgress = 100; + usMaxControlProgress = 100; + usMaxVisitProgress = 100; + } + // Else, set to INI-read values. Note that the rest of the function now reads these instead of referring + // to the long variable names. + else + { + usMaxKillsProgress = gGameExternalOptions.ubGameProgressPortionKills; + usMaxIncomeProgress = gGameExternalOptions.ubGameProgressPortionIncome; + usMaxControlProgress = gGameExternalOptions.ubGameProgressPortionControl; + usMaxVisitProgress = gGameExternalOptions.ubGameProgressPortionVisited; + } + + // figure out the player's current mine income uiCurrentIncome = PredictIncomeFromPlayerMines(); @@ -1316,36 +1347,56 @@ UINT8 CurrentPlayerProgressPercentage(void) uiPossibleIncome = CalcMaxPlayerIncomeFromMines(); // either of these indicates a critical failure of some sort - Assert(uiPossibleIncome > 0); - Assert(uiCurrentIncome <= uiPossibleIncome); + + // HEADROCK HAM 3.6: No need to assert this. Max Income can potentially be 0 in modded games. + // Assert(uiPossibleIncome > 0); + + // HEADROCK HAM 3.6: It is now possible, with the help of facilities, + // to make more money from mines than normally possible. + // This assertion check is now obsolete. + //Assert(uiCurrentIncome <= uiPossibleIncome); + uiCurrentIncome = __min(uiPossibleIncome, uiCurrentIncome); // for a rough guess as to how well the player is doing, // we'll take the current mine income / potential mine income as a percentage + + ///////////////////////////////////////////////////////////////////////////////////////////// + // + // HEADROCK HAM 3 + // Several changes have been made here to accomodate a new type of progress generation. + // With this alternate system, the program determines the current progress from each of the + // four aspects SEPARATELY. It then compares all of them, and only the HIGHEST one determines + // our current progress. Please note that while some changes have been made, the ORIGINAL + // progress control works EXACTLY THE SAME AS IT ALWAYS DID. + // + ///////////////////////////////////////////////////////////////////////////////////////////// + //Kris: Make sure you don't divide by zero!!! if( uiPossibleIncome > 0) { - ubCurrentProgress = (UINT8) ((uiCurrentIncome * gGameExternalOptions.ubGameProgressPortionIncome) / uiPossibleIncome); + usIncomeProgress = (UINT8) ((uiCurrentIncome * usMaxIncomeProgress) / uiPossibleIncome); } else { - ubCurrentProgress = 0; + usIncomeProgress = 0; } // kills per point depends on difficulty, and should match the ratios of starting enemy populations (730/1050/1500) + // HEADROCK HAM 3: Externalized all four Kills-per-point ratios. switch( gGameOptions.ubDifficultyLevel ) { case DIF_LEVEL_EASY: - ubKillsPerPoint = 7; + ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointNovice; break; case DIF_LEVEL_MEDIUM: - ubKillsPerPoint = 10; + ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointExperienced; break; case DIF_LEVEL_HARD: - ubKillsPerPoint = 15; + ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointExpert; break; case DIF_LEVEL_INSANE: - ubKillsPerPoint = 60; // Madd - uncertain whether this number is right + ubKillsPerPoint = gGameExternalOptions.usNumKillsPerProgressPointInsane; break; default: Assert(FALSE); @@ -1354,43 +1405,60 @@ UINT8 CurrentPlayerProgressPercentage(void) } usKillsProgress = gStrategicStatus.usPlayerKills / ubKillsPerPoint; - if (usKillsProgress > gGameExternalOptions.ubGameProgressPortionKills) + if (usKillsProgress > usMaxKillsProgress) { - usKillsProgress = gGameExternalOptions.ubGameProgressPortionKills; + usKillsProgress = usMaxKillsProgress; } - // add kills progress to income progress - ubCurrentProgress += usKillsProgress; - - // 19 sectors in mining towns + 3 wilderness SAMs each count double. Balime & Meduna are extra and not required // 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 = usMaxControlProgress * CalcImportantSectorControl() / CalcTotalImportantSectors(); + if (usControlProgress > usMaxControlProgress) { - usControlProgress = gGameExternalOptions.ubGameProgressPortionControl; + usControlProgress = usMaxControlProgress; } - // add control progress - ubCurrentProgress += usControlProgress; - // WDS: Adding more ways to progress in the game // Get a ratio of sectors visited to the total number of sectors // 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(); + usVisitProgress = CountSurfaceSectorsVisited() * usMaxVisitProgress / TotalVisitableSurfaceSectors(); - // add control progress - ubCurrentProgress += usVisitProgress; + // HEADROCK HAM 3: This bit is ugly for now, unless someone can optimize it for me. + // When the "Alternate Progress Calculation" is activated, the program selects only the HIGHEST of the + // progress controls, and sets that to be the current progress, disregarding any advances in the three + // other fields. - return(ubCurrentProgress); + if (gGameExternalOptions.fAlternateProgressCalculation) + { + usHighestProgress = __max(usKillsProgress, usControlProgress); + usHighestProgress = __max(usHighestProgress, usIncomeProgress); + usHighestProgress = __max(usHighestProgress, usVisitProgress); + usCurrentProgress = usHighestProgress; + } + // Else, add them all up as normal (original progress calculation). This replaces all those lines I removed + // above where progress was summed along the way. + else + { + usCurrentProgress = usKillsProgress + usControlProgress + usIncomeProgress + usVisitProgress; + } + + // Add a static amount of points, as declared in the INI file. + usCurrentProgress += gGameExternalOptions.ubGameProgressIncrement; + + // And failsafes here. I'm not 100% sure about these though: I've never personally seen progress + // values go over 100, and I don't think they SHOULD... Can the game handle values > 100? Should it? + usCurrentProgress = __min(100, usCurrentProgress); + // No less than 0, or the minimum set in the INI file. + usCurrentProgress = __max(gGameExternalOptions.ubGameProgressMinimum, __max(0, usCurrentProgress)); + + return((UINT8)usCurrentProgress); } - UINT8 HighestPlayerProgressPercentage(void) { if( gfEditMode ) @@ -1399,7 +1467,6 @@ UINT8 HighestPlayerProgressPercentage(void) return(gStrategicStatus.ubHighestProgress); } - // monitors the highest level of progress that player has achieved so far (checking hourly), // as opposed to his immediate situation (which may be worse if he's suffered a setback). void HourlyProgressUpdate(void) diff --git a/Tactical/DisplayCover.cpp b/Tactical/DisplayCover.cpp index 9a715d31..c4d56780 100644 --- a/Tactical/DisplayCover.cpp +++ b/Tactical/DisplayCover.cpp @@ -556,7 +556,10 @@ void DisplayRangeToTarget( SOLDIERTYPE *pSoldier, INT16 sTargetGridNo ) } pSoldier->bTargetLevel = bTempTargetLevel; - swprintf( zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, Weapon[ pSoldier->inv[HANDPOS].usItem ].usRange / 10, uiHitChance ); + // HEADROCK HAM 3.6: Calculate Gun Range using formula. + UINT16 usGunRange = GunRange(&pSoldier->inv[HANDPOS]); + + swprintf( zOutputString, gzDisplayCoverText[DC_MSG__GUN_RANGE_INFORMATION], usRange / 10, usGunRange / 10, uiHitChance ); //Display the msg ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, zOutputString ); } diff --git a/Tactical/Faces.cpp b/Tactical/Faces.cpp index f147996a..69b54437 100644 --- a/Tactical/Faces.cpp +++ b/Tactical/Faces.cpp @@ -37,6 +37,9 @@ #include "Interface Items.h" #include "meanwhile.h" #include "Map Screen Interface.h" + // HEADROCK HAM 3.2: Added two includes so that a function can read values of the Gun Range/hospital location + #include "Campaign Types.h" + #include "Strategic Event Handler.h" #endif // Defines @@ -1256,7 +1259,6 @@ void HandleRenderFaceAdjustments( FACETYPE *pFace, BOOLEAN fDisplayBuffer, BOOLE UINT16 usMaximumPts = 0; CHAR16 sString[ 32 ]; UINT16 usTextWidth; - BOOLEAN fAtGunRange = FALSE; BOOLEAN fShowNumber = FALSE; BOOLEAN fShowMaximum = FALSE; SOLDIERTYPE *pSoldier; @@ -1474,34 +1476,33 @@ void HandleRenderFaceAdjustments( FACETYPE *pFace, BOOLEAN fDisplayBuffer, BOOLE case TRAIN_SELF: case TRAIN_TOWN: + // HEADROCK HAM 3.6: New assignment. + case TRAIN_MOBILE: case TRAIN_TEAMMATE: case TRAIN_BY_OTHER: sIconIndex = 3; fDoIcon = TRUE; fShowNumber = TRUE; fShowMaximum = TRUE; - // there could be bonus pts for training at gun range - if ( ( MercPtrs[ pFace->ubSoldierID ]->sSectorX == 13) && (MercPtrs[ pFace->ubSoldierID ]->sSectorY == MAP_ROW_H) && (MercPtrs[ pFace->ubSoldierID ]->bSectorZ == 0) ) - { - fAtGunRange = TRUE; - } switch( MercPtrs[ pFace->ubSoldierID ]->bAssignment ) { case( TRAIN_SELF ): - sPtsAvailable = GetSoldierTrainingPts( MercPtrs[ pFace->ubSoldierID ], MercPtrs[ pFace->ubSoldierID ]->bTrainStat, fAtGunRange, &usMaximumPts ); + sPtsAvailable = GetSoldierTrainingPts( MercPtrs[ pFace->ubSoldierID ], MercPtrs[ pFace->ubSoldierID ]->bTrainStat, &usMaximumPts ); break; case( TRAIN_BY_OTHER ): - sPtsAvailable = GetSoldierStudentPts( MercPtrs[ pFace->ubSoldierID ], MercPtrs[ pFace->ubSoldierID ]->bTrainStat, fAtGunRange, &usMaximumPts ); + sPtsAvailable = GetSoldierStudentPts( MercPtrs[ pFace->ubSoldierID ], MercPtrs[ pFace->ubSoldierID ]->bTrainStat, &usMaximumPts ); break; + // HEADROCK HAM 3.6: New assignment. Works just like Town Training. case( TRAIN_TOWN ): + case( TRAIN_MOBILE ): sPtsAvailable = GetTownTrainPtsForCharacter( MercPtrs[ pFace->ubSoldierID ], &usMaximumPts ); // divide both amounts by 10 to make the displayed numbers a little more user-palatable (smaller) sPtsAvailable = ( sPtsAvailable + 5 ) / 10; usMaximumPts = ( usMaximumPts + 5 ) / 10; break; case( TRAIN_TEAMMATE ): - sPtsAvailable = GetBonusTrainingPtsDueToInstructor( MercPtrs[ pFace->ubSoldierID ], NULL , MercPtrs[ pFace->ubSoldierID ]->bTrainStat, fAtGunRange, &usMaximumPts ); + sPtsAvailable = GetBonusTrainingPtsDueToInstructor( MercPtrs[ pFace->ubSoldierID ], NULL , MercPtrs[ pFace->ubSoldierID ]->bTrainStat, &usMaximumPts ); break; } break; diff --git a/Tactical/Handle Items.cpp b/Tactical/Handle Items.cpp index 7a776301..79f5d1b7 100644 --- a/Tactical/Handle Items.cpp +++ b/Tactical/Handle Items.cpp @@ -526,15 +526,22 @@ INT32 HandleItem( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bLevel, UINT16 usHa if((__min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto) > 0 && pSoldier->bTeam == OUR_TEAM) { - // More than 1 round - if (__min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto > 1) + if (gGameExternalOptions.usBulletHideIntensity > 0) { - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[ 62 ], pSoldier->name, __min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto ); + // HEADROCK HAM 3.5: Non-accurate assessment. + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113HAMMessage[ 2 ], pSoldier->name ); } - // 1 round else - { - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[ 63 ], pSoldier->name, __min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto ); + {// More than 1 round + if (__min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto > 1) + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[ 62 ], pSoldier->name, __min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto ); + } + // 1 round + else + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzLateLocalizedString[ 63 ], pSoldier->name, __min(pSoldier->bDoAutofire,pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft) - startAuto ); + } } } @@ -1957,6 +1964,11 @@ void HandleSoldierPickupItem( SOLDIERTYPE *pSoldier, INT32 iItemIndex, INT16 sGr // OK, if an enemy, go directly ( skip menu ) if ( pSoldier->bTeam != gbPlayerNum ) { + // HEADROCK HAM 3.5: On-screen message when militia pick up items. + if ( pSoldier->bTeam == MILITIA_TEAM ) + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113HAMMessage[4], Item[gWorldItems[ pItemPool->iItemIndex ].object.usItem].szItemName ); + } SoldierGetItemFromWorld( pSoldier, iItemIndex, sGridNo, bZLevel, NULL ); } else diff --git a/Tactical/Interface Enhanced.cpp b/Tactical/Interface Enhanced.cpp index 30700e4c..8ead5d61 100644 --- a/Tactical/Interface Enhanced.cpp +++ b/Tactical/Interface Enhanced.cpp @@ -296,16 +296,16 @@ UINT8 UsingEDBSystem() { if(guiCurrentScreen == MAP_SCREEN) //Strategic { - if(gGameExternalOptions.fEnhancedDescriptionBox == 0) + if(gGameExternalOptions.iEnhancedDescriptionBox == 0) return 1; - if(gGameExternalOptions.fEnhancedDescriptionBox == 1) + if(gGameExternalOptions.iEnhancedDescriptionBox == 1) return 1; } else //Tactical { - if(gGameExternalOptions.fEnhancedDescriptionBox == 0) + if(gGameExternalOptions.iEnhancedDescriptionBox == 0) return (UsingNewInventorySystem()==true?1:2); - if(gGameExternalOptions.fEnhancedDescriptionBox == 2) + if(gGameExternalOptions.iEnhancedDescriptionBox == 2) return (UsingNewInventorySystem()==true?1:2); } } @@ -856,8 +856,8 @@ void InitDescStatCoords() gWeaponStats[18].sX = 268; gWeaponStats[18].sY = 136; gWeaponStats[18].sValDx = 30; // 18) COL 4; ROW 1 gWeaponStats[19].sX = 268; gWeaponStats[19].sY = 148; gWeaponStats[19].sValDx = 30; // 19) COL 4; ROW 2 gWeaponStats[20].sX = 268; gWeaponStats[20].sY = 160; gWeaponStats[20].sValDx = 30; // 20) COL 4; ROW 3 - gWeaponStats[21].sX = 203; gWeaponStats[21].sY = 172; gWeaponStats[21].sValDx = 30; // 21) COL 3; ROW 4 - gWeaponStats[22].sX = 268; gWeaponStats[22].sY = 172; gWeaponStats[22].sValDx = 30; // 22) COL 4; ROW 4 + gWeaponStats[21].sX = 268; gWeaponStats[21].sY = 172; gWeaponStats[21].sValDx = 30; // 21) COL 4; ROW 4 + gWeaponStats[22].sX = 203; gWeaponStats[22].sY = 172; gWeaponStats[22].sValDx = 30; // 22) COL 3; ROW 4 // EQUALS signs gWeaponStats[23].sX = 235; gWeaponStats[23].sY = 44; gWeaponStats[23].sValDx = 0; // 23) COL 1; ROW 1 gWeaponStats[24].sX = 235; gWeaponStats[24].sY = 56; gWeaponStats[24].sValDx = 0; // 24) COL 1; ROW 2 @@ -878,8 +878,8 @@ void InitDescStatCoords() gWeaponStats[39].sX = 300; gWeaponStats[39].sY = 136; gWeaponStats[39].sValDx = 0; // 39) COL 4; ROW 1 gWeaponStats[40].sX = 300; gWeaponStats[40].sY = 148; gWeaponStats[40].sValDx = 0; // 40) COL 4; ROW 2 gWeaponStats[41].sX = 300; gWeaponStats[41].sY = 160; gWeaponStats[41].sValDx = 0; // 41) COL 4; ROW 3 - gWeaponStats[42].sX = 235; gWeaponStats[42].sY = 172; gWeaponStats[42].sValDx = 0; // 42) COL 3; ROW 4 - gWeaponStats[43].sX = 300; gWeaponStats[43].sY = 172; gWeaponStats[43].sValDx = 0; // 43) COL 4; ROW 4 + gWeaponStats[42].sX = 300; gWeaponStats[42].sY = 172; gWeaponStats[42].sValDx = 0; // 42) COL 4; ROW 4 + gWeaponStats[43].sX = 235; gWeaponStats[43].sY = 172; gWeaponStats[43].sValDx = 0; // 43) COL 3; ROW 4 // Nonregular locations for explosive radius values (COL 2; ROW 3) gWeaponStats[44].sX = 273; gWeaponStats[44].sY = 68; gWeaponStats[44].sValDx = 0; // 44) COL 2; ROW 3; LOC 1 - Starting Radius gWeaponStats[45].sX = 291; gWeaponStats[45].sY = 68; gWeaponStats[45].sValDx = 0; // 45) COL 2; ROW 3; LOC 2 - Single Radius @@ -5988,10 +5988,10 @@ void DrawMiscValues( OBJECTTYPE * gpItemDescObject ) // HIDE MUZZLE FLASH // NO DATA SHOWN, icon either appears or does not appear. // HEADROCK HAM 3.5 - Fixed this, because the count needs to be increased to skip a line! - if ( Item[ gpItemDescObject->usItem ].hidemuzzleflash > 0 && cnt<=27 ) - { - cnt++; - } + if ( Item[ gpItemDescObject->usItem ].hidemuzzleflash > 0 && cnt<=27 ) + { + cnt++; + } // BIPOD if ( Item[ gpItemDescObject->usItem ].bipod != 0 && cnt<=27 ) diff --git a/Tactical/Interface Items.cpp b/Tactical/Interface Items.cpp index 235b7f95..e2cc56b0 100644 --- a/Tactical/Interface Items.cpp +++ b/Tactical/Interface Items.cpp @@ -2508,8 +2508,22 @@ void INVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObjec // SetFontForeground( FONT_MCOLOR_DKGRAY ); // break; //} + + // HEADROCK HAM 3.4: Get estimate of bullets left. + if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) + { + // Soldier doesn't know. + EstimateBulletsLeft( pSoldier, pObject ); + swprintf( pStr, L"%s", gBulletCount ); + } + else + { + swprintf( pStr, L"%d", (*pObject)[iter]->data.gun.ubGunShotsLeft ); + } + + //swprintf( pStr, L"%d", (*pObject)[iter]->data.gun.ubGunShotsLeft ); + //swprintf( pStr, L"%d", GetEstimateBulletsLeft(pSoldier, pObject) ); - swprintf( pStr, L"%d", (*pObject)[iter]->data.gun.ubGunShotsLeft ); if ( uiBuffer == guiSAVEBUFFER ) { RestoreExternBackgroundRect( sNewX, sNewY, 20, 15 ); @@ -2892,7 +2906,7 @@ BOOLEAN InternalInitItemDescriptionBox( OBJECTTYPE *pObject, INT16 sX, INT16 sY, //CHRISL: Initialize coords based on EDB/NIV settings InitDescStatCoords(); InitEDBCoords(); - InitItemDescriptionBoxStartCoords( gGameExternalOptions.fEnhancedDescriptionBox ); + InitItemDescriptionBoxStartCoords( gGameExternalOptions.iEnhancedDescriptionBox ); //CHRISL: We only want this condition to be true when looking at MONEY. Not IC_MONEY since we can't actually split // things like gold nuggets or wallets. @@ -2974,10 +2988,33 @@ BOOLEAN InternalInitItemDescriptionBox( OBJECTTYPE *pObject, INT16 sX, INT16 sY, // Add button // if( guiCurrentScreen != MAP_SCREEN ) //if( guiCurrentItemDescriptionScreen != MAP_SCREEN ) + if ( GetMagSize(gpItemDescObject) <= 99 ) - swprintf( pStr, L"%d/%d", (*gpItemDescObject)[ubStatusIndex]->data.gun.ubGunShotsLeft, GetMagSize(gpItemDescObject)); + { + // HEADROCK HAM 3.4: "Bullet Hide" feature - bullet count only shown during combat if character is competent enough. + if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) + { + EstimateBulletsLeft( pSoldier, pObject ); + swprintf(pStr, L"%s/%d", gBulletCount, GetMagSize(gpItemDescObject) ); + } + else + { + swprintf( pStr, L"%d/%d", (*gpItemDescObject)[ubStatusIndex]->data.gun.ubGunShotsLeft, GetMagSize(gpItemDescObject)); + } + } else - swprintf( pStr, L"%d", (*gpItemDescObject)[ubStatusIndex]->data.gun.ubGunShotsLeft ); + { + // HEADROCK HAM 3.4: "Bullet Hide" feature - bullet count only shown during combat if character is competent enough. + if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) + { + EstimateBulletsLeft( pSoldier, pObject ); + swprintf( pStr, L"%s", gBulletCount ); + } + else + { + swprintf( pStr, L"%d", (*gpItemDescObject)[ubStatusIndex]->data.gun.ubGunShotsLeft ); + } + } FilenameForBPP("INTERFACE\\infobox.sti", ubString); sForeColour = ITEMDESC_AMMO_FORE; diff --git a/Tactical/Interface Panels.cpp b/Tactical/Interface Panels.cpp index 75b7b4d8..75bd8915 100644 --- a/Tactical/Interface Panels.cpp +++ b/Tactical/Interface Panels.cpp @@ -72,6 +72,8 @@ #include "MessageBoxScreen.h" #include "wordwrap.h" #include "Boxing.h" + // HEADROCK HAM 3.6: This is required for Stat Progress Bars + #include "Campaign.h" #endif //forward declarations of common classes to eliminate includes @@ -1532,27 +1534,27 @@ BOOLEAN InitializeSMPanelCoordsOld() SM_CAMMO_X = ( 428 + INTERFACE_START_X ); SM_CAMMO_Y = ( 121 + INV_INTERFACE_START_Y ); - SM_STATS_WIDTH = 30; + SM_STATS_WIDTH = 16; SM_STATS_HEIGHT = 8 ; - SM_AGI_X = ( 99 + INTERFACE_START_X ); + SM_AGI_X = ( 115 + INTERFACE_START_X ); SM_AGI_Y = ( 7 + INV_INTERFACE_START_Y ); - SM_DEX_X = ( 99 + INTERFACE_START_X ); + SM_DEX_X = ( 115 + INTERFACE_START_X ); SM_DEX_Y = ( 17 + INV_INTERFACE_START_Y ); - SM_STR_X = ( 99 + INTERFACE_START_X ); + SM_STR_X = ( 115 + INTERFACE_START_X ); SM_STR_Y = ( 27 + INV_INTERFACE_START_Y ); - SM_CHAR_X = ( 99 + INTERFACE_START_X ); + SM_CHAR_X = ( 115 + INTERFACE_START_X ); SM_CHAR_Y = ( 37 + INV_INTERFACE_START_Y ); - SM_WIS_X = ( 99 + INTERFACE_START_X ); + SM_WIS_X = ( 115 + INTERFACE_START_X ); SM_WIS_Y = ( 47 + INV_INTERFACE_START_Y ); - SM_EXPLVL_X = ( 148 + INTERFACE_START_X ); + SM_EXPLVL_X = ( 163 + INTERFACE_START_X ); SM_EXPLVL_Y = ( 7 + INV_INTERFACE_START_Y ); - SM_MRKM_X = ( 148 + INTERFACE_START_X ); + SM_MRKM_X = ( 163 + INTERFACE_START_X ); SM_MRKM_Y = ( 17 + INV_INTERFACE_START_Y ); - SM_EXPL_X = ( 148 + INTERFACE_START_X ); + SM_EXPL_X = ( 163 + INTERFACE_START_X ); SM_EXPL_Y = ( 27 + INV_INTERFACE_START_Y ); - SM_MECH_X = ( 148 + INTERFACE_START_X ); + SM_MECH_X = ( 163 + INTERFACE_START_X ); SM_MECH_Y = ( 37 + INV_INTERFACE_START_Y ); - SM_MED_X = ( 148 + INTERFACE_START_X ); + SM_MED_X = ( 163 + INTERFACE_START_X ); SM_MED_Y = ( 47 + INV_INTERFACE_START_Y ); MONEY_X = ( 460 + INTERFACE_START_X ); @@ -1905,27 +1907,27 @@ BOOLEAN InitializeSMPanelCoordsNew() SM_CAMMO_X = ( 218 + INTERFACE_START_X ); SM_CAMMO_Y = ( 49 + INV_INTERFACE_START_Y ); - SM_STATS_WIDTH = 30; + SM_STATS_WIDTH = 16; SM_STATS_HEIGHT = 8 ; - SM_AGI_X = ( 101 + INTERFACE_START_X ); + SM_AGI_X = ( 115 + INTERFACE_START_X ); SM_AGI_Y = ( 7 + INV_INTERFACE_START_Y ); - SM_DEX_X = ( 101 + INTERFACE_START_X ); + SM_DEX_X = ( 115 + INTERFACE_START_X ); SM_DEX_Y = ( 17 + INV_INTERFACE_START_Y ); - SM_STR_X = ( 101 + INTERFACE_START_X ); + SM_STR_X = ( 115 + INTERFACE_START_X ); SM_STR_Y = ( 27 + INV_INTERFACE_START_Y ); - SM_CHAR_X = ( 101 + INTERFACE_START_X ); + SM_CHAR_X = ( 115 + INTERFACE_START_X ); SM_CHAR_Y = ( 37 + INV_INTERFACE_START_Y ); - SM_WIS_X = ( 101 + INTERFACE_START_X ); + SM_WIS_X = ( 115 + INTERFACE_START_X ); SM_WIS_Y = ( 47 + INV_INTERFACE_START_Y ); - SM_EXPLVL_X = ( 150 + INTERFACE_START_X ); + SM_EXPLVL_X = ( 163 + INTERFACE_START_X ); SM_EXPLVL_Y = ( 7 + INV_INTERFACE_START_Y ); - SM_MRKM_X = ( 150 + INTERFACE_START_X ); + SM_MRKM_X = ( 163 + INTERFACE_START_X ); SM_MRKM_Y = ( 17 + INV_INTERFACE_START_Y ); - SM_EXPL_X = ( 150 + INTERFACE_START_X ); + SM_EXPL_X = ( 163 + INTERFACE_START_X ); SM_EXPL_Y = ( 27 + INV_INTERFACE_START_Y ); - SM_MECH_X = ( 150 + INTERFACE_START_X ); + SM_MECH_X = ( 163 + INTERFACE_START_X ); SM_MECH_Y = ( 37 + INV_INTERFACE_START_Y ); - SM_MED_X = ( 150 + INTERFACE_START_X ); + SM_MED_X = ( 163 + INTERFACE_START_X ); SM_MED_Y = ( 47 + INV_INTERFACE_START_Y ); MONEY_X = ( 185 + INTERFACE_START_X ); @@ -2495,6 +2497,133 @@ void RenderSMPanel( BOOLEAN *pfDirty ) } } + // HEADROCK HAM 3.6: "progress" bars showing how near the character is to "leveling up" in any stat. The bar + // is displayed behind the current stat value, as see on the character's info panel. + // This section draws TACTICAL info pages. Another section is in mapscreen.cpp and draws STRATEGIC info pages. + // The feature is toggled by Options-Menu switch, and its color is determined in the INI files. + if ( gGameSettings.fOptions[TOPTION_STAT_PROGRESS_BARS] ) + { + UINT8 *pDestBuf; + UINT32 uiDestPitchBYTES = 0; + SGPRect ClipRect; + UINT8 ubBarWidth; + UINT16 usColor = Get16BPPColor( FROMRGB( gGameExternalOptions.ubStatProgressBarsRed, gGameExternalOptions.ubStatProgressBarsGreen, gGameExternalOptions.ubStatProgressBarsBlue ) ); + //pDestBuf = LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES ); + pDestBuf = LockVideoSurface( guiSAVEBUFFER, &uiDestPitchBYTES ); + + // AGI + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sAgilityGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sAgilityGain+1)) / SubpointsPerPoint(AGILAMT,0); + ClipRect.iTop = (SM_AGI_Y-1); + ClipRect.iBottom = (SM_AGI_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_AGI_X; + ClipRect.iRight = SM_AGI_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // DEX + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sDexterityGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sDexterityGain+1)) / SubpointsPerPoint(DEXTAMT,0); + ClipRect.iTop = (SM_DEX_Y-1); + ClipRect.iBottom = (SM_DEX_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_DEX_X; + ClipRect.iRight = SM_DEX_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // STR + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sStrengthGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sStrengthGain+1)) / SubpointsPerPoint(STRAMT,0); + ClipRect.iTop = (SM_STR_Y-1); + ClipRect.iBottom = (SM_STR_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_STR_X; + ClipRect.iRight = SM_STR_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // WIS + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sWisdomGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sWisdomGain+1)) / SubpointsPerPoint(WISDOMAMT,0); + ClipRect.iTop = (SM_WIS_Y-1); + ClipRect.iBottom = (SM_WIS_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_WIS_X; + ClipRect.iRight = SM_WIS_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // MRK + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMarksmanshipGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMarksmanshipGain+1)) / SubpointsPerPoint(MARKAMT,0); + ClipRect.iTop = (SM_MRKM_Y-1); + ClipRect.iBottom = (SM_MRKM_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_MRKM_X; + ClipRect.iRight = SM_MRKM_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // LDR + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sLeadershipGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sLeadershipGain+1)) / SubpointsPerPoint(LDRAMT,0); + ClipRect.iTop = (SM_CHAR_Y-1); + ClipRect.iBottom = (SM_CHAR_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_CHAR_X; + ClipRect.iRight = SM_CHAR_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // MECH + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMechanicGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMechanicGain+1)) / SubpointsPerPoint(MECHANAMT,0); + ClipRect.iTop = (SM_MECH_Y-1); + ClipRect.iBottom = (SM_MECH_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_MECH_X; + ClipRect.iRight = SM_MECH_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // EXPLO + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExplosivesGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExplosivesGain+1)) / SubpointsPerPoint(EXPLODEAMT,0); + ClipRect.iTop = (SM_EXPL_Y-1); + ClipRect.iBottom = (SM_EXPL_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_EXPL_X; + ClipRect.iRight = SM_EXPL_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // MED + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMedicalGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sMedicalGain+1)) / SubpointsPerPoint(MEDICALAMT,0); + ClipRect.iTop = (SM_MED_Y-1); + ClipRect.iBottom = (SM_MED_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_MED_X; + ClipRect.iRight = SM_MED_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + // EXPLEVEL + if (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExpLevelGain) + { + ubBarWidth = (SM_STATS_WIDTH * (gMercProfiles[ gpSMCurrentMerc->ubProfile ].sExpLevelGain+1)) / SubpointsPerPoint(EXPERAMT, gpSMCurrentMerc->stats.bExpLevel); + ClipRect.iTop = (SM_EXPLVL_Y-1); + ClipRect.iBottom = (SM_EXPLVL_Y-1) + SM_STATS_HEIGHT; + ClipRect.iLeft = SM_EXPLVL_X; + ClipRect.iRight = SM_EXPLVL_X + ubBarWidth; + Blt16BPPBufferHatchRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, usColor ); + } + + UnLockVideoSurface( guiSAVEBUFFER ); + } + // Render faceplate //BltVideoObjectFromIndex( guiSAVEBUFFER, guiSMObjects2, 1, SM_SELMERC_NAMEPLATE_X, SM_SELMERC_NAMEPLATE_Y, VO_BLT_SRCTRANSPARENCY, NULL ); //RestoreExternBackgroundRect( SM_SELMERC_NAMEPLATE_X, SM_SELMERC_NAMEPLATE_Y, SM_SELMERC_NAMEPLATE_WIDTH, SM_SELMERC_NAMEPLATE_HEIGHT ); @@ -5433,7 +5562,36 @@ void MercFacePanelCallback( MOUSE_REGION * pRegion, INT32 iReason ) } else { - HandleLocateSelectMerc( ubSoldierID, 0 ); + // HEADROCK HAM 3.5: Shift-Click a merc's face will add him to the current selection. + if (!(gTacticalStatus.uiFlags & INCOMBAT) && _KeyDown( SHIFT ) ) + { + if ( ! (MercPtrs[ ubSoldierID ]->flags.uiStatusFlags & SOLDIER_MULTI_SELECTED ) ) + { + if ( OK_CONTROLLABLE_MERC( MercPtrs[ ubSoldierID ] ) && !( MercPtrs[ ubSoldierID ]->flags.uiStatusFlags & ( SOLDIER_VEHICLE | SOLDIER_PASSENGER | SOLDIER_DRIVER ) ) ) + { + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s added", MercPtrs[ ubSoldierID ]->name ); + MercPtrs[ gusSelectedSoldier ]->flags.uiStatusFlags |= SOLDIER_MULTI_SELECTED; + MercPtrs[ ubSoldierID ]->flags.uiStatusFlags |= SOLDIER_MULTI_SELECTED; + EndMultiSoldierSelection( TRUE ); + } + } + // A shift-click on a selected character will remove that character from the current selection. + else + { + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s removed", MercPtrs[ ubSoldierID ]->name ); + MercPtrs[ ubSoldierID ]->flags.uiStatusFlags &= (~SOLDIER_MULTI_SELECTED ); + if (ubSoldierID != gusSelectedSoldier) + { + MercPtrs[ gusSelectedSoldier ]->flags.uiStatusFlags |= SOLDIER_MULTI_SELECTED; + } + EndMultiSoldierSelection( TRUE ); + } + } + else + { + //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s selected", MercPtrs[ ubSoldierID ]->name ); + HandleLocateSelectMerc( ubSoldierID, 0 ); + } } } else diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index 5982ff90..0d502517 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -2680,15 +2680,21 @@ UINT32 CalculateCarriedWeight( SOLDIERTYPE * pSoldier ) //ADB the weight of the object is already counting stacked objects, attachments, et al uiTotalWeight += CalculateObjectWeight(&pSoldier->inv[ubLoop]); } - // for now, assume soldiers can carry 1/2 their strength in KGs without penalty. - // instead of multiplying by 100 for percent, and then dividing by 10 to account - // for weight units being in 10ths of kilos, not kilos... we just start with 10 instead of 100! + // Every point over 80 counts double. ubStrengthForCarrying = EffectiveStrength( pSoldier ); if ( ubStrengthForCarrying > 80 ) { ubStrengthForCarrying += (ubStrengthForCarrying - 80); } - uiPercent = (10 * uiTotalWeight) / ( ubStrengthForCarrying / 2 ); + + // for now, assume soldiers can carry 1/2 their strength in KGs without penalty. + // instead of multiplying by 100 for percent, and then dividing by 10 to account + // for weight units being in 10ths of kilos, not kilos... we just start with 10 instead of 100! + + // HEADROCK HAM 3: STR required per 1/2 kilo has been externalized. Can someone tidy this up though? The + // formula works great, but it's damn ugly now. + uiPercent = (UINT32)(((FLOAT)10 * (FLOAT)gGameExternalOptions.iStrengthToLiftHalfKilo) * uiTotalWeight) / ( ubStrengthForCarrying / 2 ); + return( uiPercent ); } @@ -3152,9 +3158,12 @@ BOOLEAN EmptyWeaponMagazine( OBJECTTYPE * pWeapon, OBJECTTYPE *pAmmo, UINT32 sub { CreateAmmo((*pWeapon)[subObject]->data.gun.usGunAmmoItem, pAmmo, (*pWeapon)[subObject]->data.gun.ubGunShotsLeft); - (*pWeapon)[subObject]->data.gun.ubGunShotsLeft = 0; - (*pWeapon)[subObject]->data.gun.ubGunAmmoType = 0; - //(*pWeapon)[subObject]->data.gun.usGunAmmoItem = 0; // leaving the ammo item the same for auto-reloading purposes + (*pWeapon)[subObject]->data.gun.ubGunShotsLeft = 0; + (*pWeapon)[subObject]->data.gun.ubGunAmmoType = 0; + // HEADROCK HAM 3.5: Leaving the ammo inside the gun causes EDB stats to display values as though the magazine + // still gives some effects (like autopen reduction, range bonus, etcetera). I'm going to try to work around + // this issue. + (*pWeapon)[subObject]->data.gun.usGunAmmoItem = 0; // leaving the ammo item the same for auto-reloading purposes // Play some effects! usReloadSound = Weapon[ pWeapon->usItem ].sReloadSound; @@ -3174,13 +3183,20 @@ BOOLEAN EmptyWeaponMagazine( OBJECTTYPE * pWeapon, OBJECTTYPE *pAmmo, UINT32 sub } else { + // HEADROCK HAM 3.5: Clear the ammo type and magazine on player command. This will remove all bonuses by + // the ammo and allow viewing the gun's normal stats. It will also change the weapon's ammocolor back to grey. + (*pWeapon)[subObject]->data.gun.ubGunAmmoType = 0; + (*pWeapon)[subObject]->data.gun.usGunAmmoItem = 0; //CHRISL: Clear the contents of pAmmo just in case pAmmo->initialize(); return( FALSE ); } } -INT8 FindAmmo( SOLDIERTYPE * pSoldier, UINT8 ubCalibre, UINT16 ubMagSize, INT8 bExcludeSlot ) +// HEADROCK HAM 3.3: Added an additional argument which helps the program pick a magazine +// that matches the ammotype currently used in the weapon. This makes for much smarter +// ammo selection. +INT8 FindAmmo( SOLDIERTYPE * pSoldier, UINT8 ubCalibre, UINT16 ubMagSize, UINT8 ubAmmoType, INT8 bExcludeSlot ) { INT8 bLoop; INT8 capLoop = 0; @@ -3211,7 +3227,10 @@ INT8 FindAmmo( SOLDIERTYPE * pSoldier, UINT8 ubCalibre, UINT16 ubMagSize, INT8 b { stackCap = __max(stackCap, pSoldier->inv[bLoop][i]->data.ubShotsLeft); } - if(stackCap > curCap) + // If found a similar-sized magazine to the best one found so far, but this new one + // has the same ammotype as specified in the arguments, then this is a better choice! + if(stackCap > curCap || + (stackCap == curCap && Magazine[pItem->ubClassIndex].ubAmmoType == ubAmmoType)) { curCap = stackCap; capLoop = bLoop; @@ -3264,7 +3283,7 @@ INT8 FindAmmoToReload( SOLDIERTYPE * pSoldier, INT8 bWeaponIn, INT8 bExcludeSlot // return( bSlot ); //} // look for any ammo that matches which is of the same calibre and magazine size - bSlot = FindAmmo( pSoldier, Weapon[pObj->usItem].ubCalibre, GetMagSize(pObj), bExcludeSlot ); + bSlot = FindAmmo( pSoldier, Weapon[pObj->usItem].ubCalibre, GetMagSize(pObj), GetAmmoType(pObj), bExcludeSlot ); if (bSlot != NO_SLOT) { return( bSlot ); @@ -3272,7 +3291,7 @@ INT8 FindAmmoToReload( SOLDIERTYPE * pSoldier, INT8 bWeaponIn, INT8 bExcludeSlot else { // look for any ammo that matches which is of the same calibre (different size okay) - return( FindAmmo( pSoldier, Weapon[pObj->usItem].ubCalibre, ANY_MAGSIZE, bExcludeSlot ) ); + return( FindAmmo( pSoldier, Weapon[pObj->usItem].ubCalibre, ANY_MAGSIZE, GetAmmoType(pObj), bExcludeSlot ) ); } } else @@ -3970,6 +3989,8 @@ void EjectAmmoAndPlace(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj) CreateAmmo((*pObj)[0]->data.gun.usGunAmmoItem, &gTempObject, (*pObj)[0]->data.gun.ubGunShotsLeft); (*pObj)[0]->data.gun.ubGunShotsLeft = 0; (*pObj)[0]->data.gun.usGunAmmoItem = NONE; + // HEADROCK HAM 3.5: Clear ammo type + (*pObj)[0]->data.gun.ubGunAmmoType = NONE; if ( pSoldier ) { if ( !AutoPlaceObject( pSoldier, &gTempObject, FALSE ) ) @@ -7104,7 +7125,7 @@ INT16 GetBurstToHitBonus( OBJECTTYPE * pObj, BOOLEAN fProneStance ) 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 ) + if ( gGameExternalOptions.ubRealisticTracers != 1 ) bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].bursttohitbonus ; for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { @@ -7234,7 +7255,7 @@ INT16 GetAutoToHitBonus( OBJECTTYPE * pObj, BOOLEAN fProneStance ) // 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 ) + if ( gGameExternalOptions.ubRealisticTracers != 1 ) bonus += Item[(*pObj)[0]->data.gun.usGunAmmoItem].autofiretohitbonus ; for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) { @@ -7648,6 +7669,26 @@ UINT8 GetPercentTunnelVision( SOLDIERTYPE * pSoldier ) } } + // HEADROCK HAM 3.2: Further increase tunnel-vision for cowering characters. + if (gGameExternalOptions.ubCoweringReducesSightRange == 1 || gGameExternalOptions.ubCoweringReducesSightRange == 3) + { + INT8 bTolerance = CalcSuppressionTolerance( pSoldier ); + + // Make sure character is cowering. + if ( pSoldier->aiData.bShock >= bTolerance && gGameExternalOptions.ubMaxSuppressionShock > 0 && + bonus < 100 ) + { + // Calculates a "Flat" tunnel vision percentage + UINT8 ubNormalCoweringTunnelVision = (100 * pSoldier->aiData.bShock) / gGameExternalOptions.ubMaxSuppressionShock; + + // Apply that percentage to the current tunnel vision + UINT16 usActualCoweringTunnelVision = bonus + (((100-bonus) * ubNormalCoweringTunnelVision) / 100); + + // At shock 0, tunnel vision remains unchanged. At full shock, tunnel vision is full (100%) + bonus = __min(100,usActualCoweringTunnelVision); + } + } + if ( PTR_OURTEAM ) // Madd: adjust tunnel vision by difficulty level return( bonus ); else @@ -8495,106 +8536,122 @@ INT16 GetMinRangeForAimBonus( OBJECTTYPE * pObj ) UINT8 AllowedAimingLevels(SOLDIERTYPE * pSoldier) { UINT8 aimLevels = 4; - float iScopeBonus = 0; + UINT16 usScopeBonus = 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) + if ( gGameSettings.fOptions[TOPTION_AIM_LEVEL_RESTRICTION] ) // Options Menu setting. + { + // HEADROCK HAM B2.6: Dynamic aiming level restrictions based on gun type and attachments. + // HEADROCK HAM 3.5: Revamped this - it was illogically constructed. + if ( gGameExternalOptions.fDynamicAimingTime ) { - 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; + 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; - } - else - { - return 4; - } + + // Define basic (no attachments), and absolute maximums + if (weaponType == GUN_PISTOL || weaponType == GUN_M_PISTOL || fTwoHanded == 0) + { + maxAimForType = 2; + aimLevels = 1; + maxAimWithoutBipod = 2; + } + else if (weaponType == GUN_SHOTGUN || weaponType == GUN_LMG || weaponType == GUN_SMG) + { + maxAimForType = 3; + aimLevels = 2; + maxAimWithoutBipod = 3; + } + else if ((weaponType == GUN_AS_RIFLE || weaponType == GUN_RIFLE ) && weaponRange <= 500) + { + maxAimForType = 4; + aimLevels = 2; + maxAimWithoutBipod = 3; + } + else if (((weaponType == GUN_AS_RIFLE || weaponType == GUN_RIFLE) && weaponRange > 500) || + (weaponType == GUN_SN_RIFLE && weaponRange <= 500)) + { + maxAimForType = 6; + aimLevels = 3; + maxAimWithoutBipod = 4; + } + else if (weaponType == GUN_SN_RIFLE && weaponRange > 500) + { + maxAimForType = 8; + aimLevels = 4; + maxAimWithoutBipod = 3; + } + 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; + // Determine whether a bipod is being used (prone) + if (GetBipodBonus(&pSoldier->inv[pSoldier->ubAttackingHand])>0 && gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE ) + { + fUsingBipod = TRUE; + } + + usScopeBonus = ( GetMinRangeForAimBonus(&pSoldier->inv[pSoldier->ubAttackingHand]) * 10 ) / gGameExternalOptions.ubStraightSightRange; + + if ( usScopeBonus >= 50 ) // Scope Min Range >= 7 Tiles + { + aimLevels *= 2; + } + + else if ( usScopeBonus >= 30 ) // Scope Min Range >= 4 Tiles + { + aimLevels = (UINT8)((float)(aimLevels+1) * (float)1.5); + } + + else if ( usScopeBonus >= 15 ) // Scope Min Range >= 2 Tiles + { + aimLevels = (UINT8)((float)(aimLevels+1) * (float)1.3); + } + + // Smaller scopes increase by one. + else if ( usScopeBonus > 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); + } } - - 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) + else // JA2 1.13 Basic aiming restrictions (8 levels for 10x scope, 6 levels for 7x scope) { - 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; - - if ( allowed && IsScoped( &pSoldier->inv[pSoldier->ubAttackingHand] ) ) - { - iScopeBonus = ( (float)gGameExternalOptions.ubStraightSightRange * GetMinRangeForAimBonus(&pSoldier->inv[pSoldier->ubAttackingHand]) / 100 ); - - if ( iScopeBonus >= ( (float)gGameExternalOptions.ubStraightSightRange * 0.3) ) // >= 30% of sight range (~4 tiles by default) - { - aimLevels += 2; - } - - if ( iScopeBonus >= ( (float)gGameExternalOptions.ubStraightSightRange * 0.6) ) // >= 60% of sight range (~9 tiles by default) - { - aimLevels += 2; + if ( !IsScoped( &pSoldier->inv[pSoldier->ubAttackingHand] ) ) + { + // No scope. 4 Allowed. + return (4); + } + + usScopeBonus = ( GetMinRangeForAimBonus(&pSoldier->inv[pSoldier->ubAttackingHand]) * 10 ) / gGameExternalOptions.ubStraightSightRange; + + if ( usScopeBonus >= 50 ) // Scope Min Range >= 7 Tiles (Sniper Scope) + { + aimLevels += 2; + } + if ( usScopeBonus >= 30 ) // Scope Min Range >= 4 Tiles (Battle Scope) + { + aimLevels += 2; } } } @@ -8883,3 +8940,21 @@ INT16 GetBasicStealthBonus( OBJECTTYPE * pObj ) return( bonus ); } +// HEADROCK HAM 3.6: This is meant to squash an exploit where a backpack can be moved to your hand to avoid AP penalties. +INT8 FindBackpackOnSoldier( SOLDIERTYPE * pSoldier ) +{ + INT8 bLoop; + + for (bLoop = 0; bLoop < NUM_INV_SLOTS ; bLoop++) + { + if (pSoldier->inv[bLoop].exists()) + { + if (Item[pSoldier->inv[bLoop].usItem].usItemClass == IC_LBEGEAR && + LoadBearingEquipment[Item[pSoldier->inv[bLoop].usItem].ubClassIndex].lbeClass == BACKPACK) + { + return( bLoop ); + } + } + } + return( ITEM_NOT_FOUND ); +} \ No newline at end of file diff --git a/Tactical/Items.h b/Tactical/Items.h index 9a1ea682..ed00727c 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -18,7 +18,7 @@ extern UINT8 SlotToPocket[7]; extern BOOLEAN WeaponInHand( SOLDIERTYPE * pSoldier ); -INT8 FindAmmo( SOLDIERTYPE * pSoldier, UINT8 ubCalibre, UINT16 ubMagSize, INT8 bExcludeSlot ); +INT8 FindAmmo( SOLDIERTYPE * pSoldier, UINT8 ubCalibre, UINT16 ubMagSize, UINT8 ubAmmoType, INT8 bExcludeSlot ); INT8 FindBestWeaponIfCurrentIsOutOfRange(SOLDIERTYPE * pSoldier, INT8 bCurrentWeaponIndex, UINT16 bWantedRange); @@ -343,7 +343,8 @@ INT16 GetDesertCamoBonus( OBJECTTYPE * pObj ); INT16 GetWornSnowCamo( SOLDIERTYPE * pSoldier ); INT16 GetSnowCamoBonus( OBJECTTYPE * pObj ); - +// HEADROCK HAM 3.6: Looks for a backpack anywhere on this character. +INT8 FindBackpackOnSoldier( SOLDIERTYPE * pSoldier ); #endif diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index bb100151..01f335e9 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -48,6 +48,8 @@ #include "fresh_header.h" #include "test_space.h" #include "WorldDat.h" +// HEADROCK HAM 3.6: This must be included, for testing whether Bloodcats and Enemies can see one another. +#include "Campaign Types.h" //forward declarations of common classes to eliminate includes class OBJECTTYPE; @@ -2009,20 +2011,47 @@ INT32 SoldierToSoldierLineOfSightTest( SOLDIERTYPE * pStartSoldier, SOLDIERTYPE fOk = CalculateSoldierZPos( pStartSoldier, LOS_POS, &dStartZPos ); CHECKF( fOk ); - if ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_N ) + // HEADROCK HAM 3.6: Location of static sectors externalized, and there can be more than one. Also, modders can + // determine whether bloodcats really are blind to enemies here at all. + UINT8 ubSectorID = SECTOR(gWorldSectorX,gWorldSectorY); + UINT8 PlacementType = gBloodcatPlacements[ ubSectorID ][ 0 ].PlacementType; + // Does sector contain a Bloodcat Garrison? + if (PlacementType == BLOODCAT_PLACEMENT_STATIC) { - // in the bloodcat arena sector, skip sight between army & bloodcats - if ( pStartSoldier->bTeam == ENEMY_TEAM && pEndSoldier->bTeam == CREATURE_TEAM ) + // Are bloodcats set to forgo attacking enemies? + if (gBloodcatPlacements[ ubSectorID ][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation == QUEENS_CIV_GROUP) { - return( 0 ); + // skip sight between army & bloodcats + if ( pStartSoldier->bTeam == ENEMY_TEAM && pEndSoldier->bTeam == CREATURE_TEAM && pEndSoldier->ubBodyType == BLOODCAT ) + { + return( 0 ); + } + if ( pStartSoldier->bTeam == CREATURE_TEAM && pStartSoldier->ubBodyType == BLOODCAT && pEndSoldier->bTeam == ENEMY_TEAM ) + { + return( 0 ); + } } - if ( pStartSoldier->bTeam == CREATURE_TEAM && pEndSoldier->bTeam == ENEMY_TEAM ) + else if (gBloodcatPlacements[ ubSectorID ][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation > NON_CIV_GROUP) { - return( 0 ); + // Bloodcats in this sector belong to a faction. They adhere to certain rules as a result. + if ( pEndSoldier->bTeam == CREATURE_TEAM && pEndSoldier->ubBodyType == BLOODCAT && pStartSoldier->bSide != gbPlayerNum) + { + // Target is a bloodcat. He can't be spotted by civilians no matter what. + { + return ( 0 ); + } + } + else if ( pStartSoldier->bTeam == CREATURE_TEAM && pStartSoldier->ubBodyType == BLOODCAT ) + { + // Source is a bloodcat. He can only spot player-side soldiers, and only if hostile. + if ( pEndSoldier->bSide != gbPlayerNum || pStartSoldier->aiData.bNeutral ) + { + return ( 0 ); + } + } } } - if (pStartSoldier->flags.uiStatusFlags & SOLDIER_MONSTER) { // monsters use smell instead of sight! @@ -4002,11 +4031,11 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA } // 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) + else if (gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) && AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect ) { UINT16 iBulletsLeft, iBulletsPerTracer; - iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; + iBulletsPerTracer = gGameExternalOptions.ubNumBulletsPerTracer; iBulletsLeft = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunShotsLeft + pFirer->bDoBurst; if ((((iBulletsLeft - (pFirer->bDoBurst - 1)) / iBulletsPerTracer) - ((iBulletsLeft - pFirer->bDoBurst) / iBulletsPerTracer)) == 1) @@ -4345,11 +4374,11 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA 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) + if (gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && (pFirer->bDoAutofire > 0 || pFirer->bDoBurst > 0) && AmmoTypes[ pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType ].tracerEffect ) { UINT16 iBulletsLeft, iBulletsPerTracer; - iBulletsPerTracer = gGameExternalOptions.iNumBulletsPerTracer; + iBulletsPerTracer = gGameExternalOptions.ubNumBulletsPerTracer; 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 @@ -4686,9 +4715,13 @@ void MoveBullet( INT32 iBullet ) if ( IS_MERC_BODY_TYPE( MercPtrs[pStructure->usStructureID] ) ) { + // HEADROCK HAM 3.3: Externalized distance at which characters suffer from friendly suppression. + // previously relied on minimum distance at which characters may suffer from friendly fire HITS. + UINT16 MIN_DIST_FOR_SCARE_FRIENDS = gGameExternalOptions.usMinDistanceFriendlySuppression; + // apply suppression, regardless of friendly or enemy // except if friendly, not within a few tiles of shooter - if ( MercPtrs[ pStructure->usStructureID ]->bSide != pBullet->pFirer->bSide || pBullet->iLoop > MIN_DIST_FOR_HIT_FRIENDS ) + if ( MercPtrs[ pStructure->usStructureID ]->bSide != pBullet->pFirer->bSide || pBullet->iLoop > MIN_DIST_FOR_SCARE_FRIENDS ) { // buckshot has only a 1 in 2 chance of applying a suppression point if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) @@ -4915,7 +4948,7 @@ void MoveBullet( INT32 iBullet ) // 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))) + || ((gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.ubRealisticTracers == 0 && fTracer == TRUE))) { INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS; @@ -5205,7 +5238,7 @@ void MoveBullet( INT32 iBullet ) // 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))) + || ((gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.ubRealisticTracers == 0 && fTracer == TRUE))) { INT8 bStepsPerMove = STEPS_FOR_BULLET_MOVE_TRAILS; diff --git a/Tactical/Merc Entering.cpp b/Tactical/Merc Entering.cpp index fa8c7a25..91d1c7e2 100644 --- a/Tactical/Merc Entering.cpp +++ b/Tactical/Merc Entering.cpp @@ -8,6 +8,8 @@ #include "stdlib.h" #include "debug.h" //#include "soldier control.h" + // HEADROCK HAM 3.5: Strange that this wasn't included. + #include "GameSettings.h" #include "weapons.h" #include "handle items.h" #include "worlddef.h" @@ -39,6 +41,8 @@ #include "Dialogue Control.h" #include "Music Control.h" #include "Tactical Save.h" + // HEADROCK HAM 3.5: Need this to see if enemies present at starting sector + #include "Overhead.h" #endif @@ -476,7 +480,8 @@ void HandleHeliDrop( ) { // Add merc to sector MercPtrs[ gusHeliSeats[ cnt ] ]->ubStrategicInsertionCode = INSERTION_CODE_NORTH; - UpdateMercInSector( MercPtrs[ gusHeliSeats[ cnt ] ], startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Externalized! + UpdateMercInSector( MercPtrs[ gusHeliSeats[ cnt ] ], gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); // Check for merc arrives quotes... HandleMercArrivesQuotes( MercPtrs[ gusHeliSeats[ cnt ] ] ); @@ -612,7 +617,8 @@ void HandleHeliDrop( ) // Change insertion code MercPtrs[ gusHeliSeats[ gbCurDrop ] ]->ubStrategicInsertionCode = INSERTION_CODE_NORTH; - UpdateMercInSector( MercPtrs[ gusHeliSeats[ gbCurDrop ] ], startingX, startingY, startingZ ); + // HEADROCK HAM 3.5: Externalized! + UpdateMercInSector( MercPtrs[ gusHeliSeats[ gbCurDrop ] ], gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ ); //EVENT_SetSoldierPosition( MercPtrs[ gusHeliSeats[ gbCurDrop ] ], sWorldX, sWorldY ); // IF the first guy down, set squad! @@ -777,6 +783,11 @@ void HandleHeliDrop( ) // End fFadingHeliOut = TRUE; + + // HEADROCK HAM 3.5: Update now, in case the LZ is still in a "RED" airspace sector. This is only + // required if the sector is free of enemies... but still required. Will run immediately after the + // helicopter is gone. + UpdateAirspaceControl( ); break; } @@ -812,13 +823,25 @@ void HandleFirstHeliDropOfGame( ) CallAvailableEnemiesTo( gsGridNoSweetSpot ); // Move to header file... - AddExtraItems( startingX, startingY, startingZ, true ); + // HEADROCK HAM 3.5: Externalized! + AddExtraItems( gGameExternalOptions.ubDefaultArrivalSectorX, gGameExternalOptions.ubDefaultArrivalSectorY, startingZ, true ); - // Say quote..... - SayQuoteFromAnyBodyInSector( QUOTE_ENEMY_PRESENCE ); - - // Start music - SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); + // HEADROCK HAM 3.5: Starting sector externalized - might not contain enemies at all! + if (NumEnemyInSector( ) > 0) + //if ( NumEnemiesInAnySector( gWorldSectorX, gWorldSectorY, 0 ) > 0 ) + { + // Say quote..... + SayQuoteFromAnyBodyInSector( QUOTE_ENEMY_PRESENCE ); + // Start music + SetMusicMode( MUSIC_TACTICAL_ENEMYPRESENT ); + } + else + { + // Say quote..... + SayQuoteFromAnyBodyInSector( QUOTE_MERC_REACHED_DESTINATION ); + // Start music + SetMusicMode( MUSIC_TACTICAL_NOTHING ); + } gfFirstHeliRun = FALSE; @@ -826,6 +849,7 @@ void HandleFirstHeliDropOfGame( ) // Send message to turn on ai again.... CharacterDialogueWithSpecialEvent( 0, 0, 0, DIALOGUE_TACTICAL_UI , FALSE , FALSE , DIALOGUE_SPECIAL_EVENT_ENABLE_AI ,0, 0 ); + } diff --git a/Tactical/Merc Hiring.cpp b/Tactical/Merc Hiring.cpp index f7bb302b..907bbf9b 100644 --- a/Tactical/Merc Hiring.cpp +++ b/Tactical/Merc Hiring.cpp @@ -78,8 +78,9 @@ extern BOOLEAN gfFirstHeliRun; // ATE: Globals that dictate where the mercs will land once being hired // Default to Omerta // Saved in general saved game structure -INT16 gsMercArriveSectorX = 9; -INT16 gsMercArriveSectorY = 1; +// HEADROCK HAM 3.5: Externalized coordinates +INT16 gsMercArriveSectorX = gGameExternalOptions.ubDefaultArrivalSectorX; +INT16 gsMercArriveSectorY = gGameExternalOptions.ubDefaultArrivalSectorY; void CheckForValidArrivalSector( ); @@ -317,16 +318,20 @@ void MercArrivesCallback( UINT8 ubSoldierID ) if (!is_networked) { // hayden - maybe you want to duke it out in omerta ;) - if( !DidGameJustStart() && gsMercArriveSectorX == 9 && gsMercArriveSectorY == 1 ) - { - //Mercs arriving in A9. This sector has been deemed as the always safe sector. - //Seeing we don't support entry into a hostile sector (except for the beginning), - //we will nuke any enemies in this sector first. - if( gWorldSectorX != 9 || gWorldSectorY != 1 || gbWorldSectorZ ) - { - EliminateAllEnemies( (UINT8)gsMercArriveSectorX, (UINT8)gsMercArriveSectorY ); - } - } + // HEADROCK HAM 3.5: Externalized starting (safe) sector + // HEADROCK HAM 3.5: Actually, this is really ridiculous. Why should enemies at the LZ be eliminated at all? + // I'm taking the initiative and removing this from the code. Mainly because it ends up interfering with + // externalized LZs combined with other features like "Always Real Time" and "Forced Turn Based". + //if( !DidGameJustStart() && gsMercArriveSectorX == gGameExternalOptions.ubDefaultArrivalSectorX && gsMercArriveSectorY == gGameExternalOptions.ubDefaultArrivalSectorY ) + // { + // //Mercs arriving in A9. This sector has been deemed as the always safe sector. + // //Seeing we don't support entry into a hostile sector (except for the beginning), + // //we will nuke any enemies in this sector first. + // if( gWorldSectorX != gGameExternalOptions.ubDefaultArrivalSectorX || gWorldSectorY != gGameExternalOptions.ubDefaultArrivalSectorY || gbWorldSectorZ ) + // { + // EliminateAllEnemies( (UINT8)gsMercArriveSectorX, (UINT8)gsMercArriveSectorY ); + // } + // } } // This will update ANY soldiers currently schedules to arrive too diff --git a/Tactical/Morale.cpp b/Tactical/Morale.cpp index 1fd53eaa..8d458096 100644 --- a/Tactical/Morale.cpp +++ b/Tactical/Morale.cpp @@ -21,6 +21,8 @@ #include "mapscreen.h" #include "Soldier macros.h" #include "Event Pump.h" + // HEADROCK HAM 3.5: Added for facility effect on morale + #include "Facilities.h" #endif #include "connect.h" @@ -137,14 +139,19 @@ void DecayTacticalMorale( SOLDIERTYPE * pSoldier ) void DecayStrategicMorale( SOLDIERTYPE * pSoldier ) { + // HEADROCK HAM 3.5: Strategic Morale Mod no longer normalizes to 0 by default. In fact, a local facility can + // cause normalization to another value (positive or negative!), based on the activity that the character is + // currently performing. It can literally reduce morale to ridiculously low amounts if you don't watch your + // assignments in some sectors. + // decay the modifier! if (pSoldier->aiData.bStrategicMoraleMod > 0) { - pSoldier->aiData.bStrategicMoraleMod = __max( 0, pSoldier->aiData.bStrategicMoraleMod - (8 - pSoldier->aiData.bStrategicMoraleMod / 10) ); + pSoldier->aiData.bStrategicMoraleMod = __max( 0, pSoldier->aiData.bStrategicMoraleMod - (8 - (pSoldier->aiData.bStrategicMoraleMod / 10)) ); } else { - pSoldier->aiData.bStrategicMoraleMod = __min( 0, pSoldier->aiData.bStrategicMoraleMod + (6 + pSoldier->aiData.bStrategicMoraleMod / 10) ); + pSoldier->aiData.bStrategicMoraleMod = __min( 0, pSoldier->aiData.bStrategicMoraleMod + (6 + (pSoldier->aiData.bStrategicMoraleMod / 10)) ); } } @@ -295,6 +302,36 @@ void RefreshSoldierMorale( SOLDIERTYPE * pSoldier ) iActualMorale = __min( 100, iActualMorale ); iActualMorale = __max( 0, iActualMorale ); + + UINT8 ubMaxMorale = 100; + // HEADROCK HAM 3.5: Local facilities may decrease the total morale allowed. + for (UINT16 cnt = 0; cnt < NUM_FACILITY_TYPES; cnt++) + { + if (gFacilityLocations[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][cnt].fFacilityHere) + { + if (cnt == (UINT16)pSoldier->sFacilityTypeOperated && // Soldier is operating this facility + GetSoldierFacilityAssignmentIndex( pSoldier ) != -1) + { + UINT8 ubFacilityType = (UINT8)cnt; + UINT8 ubAssignmentType = GetSoldierFacilityAssignmentIndex( pSoldier ); + // Check this facility both for an assignment-specific AND ambient value. Use it if it's the lowest + // encountered yet. + ubMaxMorale = __min(ubMaxMorale, (UINT8)GetFacilityModifier(FACILITY_MAX_MORALE, ubFacilityType, ubAssignmentType )); + } + else // Soldier is not operating this facility + { + // Check this facility for an AMBIENT Maximum Morale limit. Use it if it's the lowest encountered yet. + ubMaxMorale = __min(ubMaxMorale, (UINT8)GetFacilityModifier(FACILITY_MAX_MORALE, (UINT8)cnt, FAC_AMBIENT)); + } + } + } + + if (ubMaxMorale > 0 && iActualMorale > ubMaxMorale) + { + // Normalize to Max Morale + iActualMorale = (iActualMorale + ubMaxMorale) / 2; + } + pSoldier->aiData.bMorale = (INT8) iActualMorale; // update mapscreen as needed diff --git a/Tactical/Overhead Types.h b/Tactical/Overhead Types.h index 2f5fd46b..de8b1c25 100644 --- a/Tactical/Overhead Types.h +++ b/Tactical/Overhead Types.h @@ -265,8 +265,9 @@ enum WorldDirections // Starting Sector -const int startingX = 9; -const int startingY = 1; +// HEADROCK HAM 3.5: Externalized. +//UINT8 startingX = 9; +//UINT8 startingY = 1; const int startingZ = 0; diff --git a/Tactical/Overhead.cpp b/Tactical/Overhead.cpp index 7f71c1e9..215986ba 100644 --- a/Tactical/Overhead.cpp +++ b/Tactical/Overhead.cpp @@ -180,6 +180,8 @@ void HandleEndDemoInCreatureLevel( ); void DeathTimerCallback( void ); void CaptureTimerCallback( void ); +// HEADROCK HAM 3.6: Define now. +void MilitiaChangesSides( void ); extern void CheckForAlertWhenEnemyDies( SOLDIERTYPE * pDyingSoldier ); extern void PlaySoldierFootstepSound( SOLDIERTYPE *pSoldier ); @@ -2224,6 +2226,22 @@ BOOLEAN HandleGotoNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving, BOOLE // better stop and reconsider what to do... SetNewSituation( pSoldier ); ActionDone( pSoldier ); + + // HEADROCK HAM 3.6: Militia can now place flags when they spot landmines. + if (gGameExternalOptions.fMilitiaPlaceBlueFlags && + pSoldier->bTeam == MILITIA_TEAM) + { + // This line causes the screen to focus on the gridno if it is not currently visible. + // Is it desirable when militia spot mines? Probably not. Turned off for now. + //LocateGridNo( sMineGridNo ); + + // Flash gridno + ITEM_POOL *pItemPool = NULL; + GetItemPool( sMineGridNo, &pItemPool, 0 ); + SetItemPoolLocator( pItemPool ); + // Add flag + AddBlueFlag(sMineGridNo,1); + } } } } @@ -3593,7 +3611,17 @@ void HandleNPCTeamMemberDeath( SOLDIERTYPE *pSoldierOld ) if ( pSoldierOld->ubAttackerID != NOBODY ) { // also treat this as murder - but player will never be blamed for militia death he didn't cause - HandleMurderOfCivilian( pSoldierOld, pSoldierOld->flags.fIntendedTarget ); + // HEADROCK HAM 3.6: Actually this function never runs for militia (see function for details) + //HandleMurderOfCivilian( pSoldierOld, pSoldierOld->flags.fIntendedTarget ); + + // HEADROCK HAM 3.6: INI setting can cause militia to turn ONLY of they are killed intentionally + if (pSoldierOld->flags.fIntendedTarget // Must be intentional + && gGameExternalOptions.ubCanMilitiaBecomeHostile > 0 // INI setting + && pSoldierOld->bSide == gbPlayerNum // Must not be hostile by now + ) + { + MilitiaChangesSides(); // Militia turn on you. + } } HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_NATIVE_KILLED, gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); @@ -3854,7 +3882,12 @@ void MakeCivHostile( SOLDIERTYPE *pSoldier, INT8 bNewSide ) } if ( pSoldier->aiData.bNeutral ) { - SetSoldierNonNeutral( pSoldier ); + // HEADROCK HAM 3.6: INI Setting decides whether non-combat civs can become hostile + if (gGameExternalOptions.fCanTrueCiviliansBecomeHostile || + !IS_CIV_BODY_TYPE(pSoldier)) + { + SetSoldierNonNeutral( pSoldier ); + } RecalculateOppCntsDueToNoLongerNeutral( pSoldier ); } } @@ -3979,6 +4012,13 @@ SOLDIERTYPE * CivilianGroupMemberChangesSides( SOLDIERTYPE * pAttacked ) gTacticalStatus.fCivGroupHostile[ pNewAttacked->ubCivilianGroup ] = CIV_GROUP_WILL_EVENTUALLY_BECOME_HOSTILE; } + // HEADROCK HAM 3.6: If this sector has affiliated bloodcats, make them all hostile. + if ( gBloodcatPlacements[SECTOR(pNewAttacked->sSectorX,pNewAttacked->sSectorY)][0].PlacementType == BLOODCAT_PLACEMENT_STATIC && + gBloodcatPlacements[SECTOR(pNewAttacked->sSectorX,pNewAttacked->sSectorY)][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation == pNewAttacked->ubCivilianGroup ) + { + MakeBloodcatsHostile(); + } + return( pNewAttacked ); } @@ -6203,26 +6243,27 @@ void DeathNoMessageTimerCallback( void ) } } +// HEADROCK HAM 3.5: This function needs a Z-Level argument!!! It is screwing up garrisons, when a battle is won underneath +// a sector. void RemoveStaticEnemiesFromSectorInfo( INT16 sMapX, INT16 sMapY, INT8 bMapZ ) { - if (!bMapZ) // Battle ended Above-ground - { - SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); + if (!bMapZ) // Battle ended Above-ground + { + SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] ); - pSectorInfo->ubNumAdmins = pSectorInfo->ubNumTroops = pSectorInfo->ubNumElites = 0; - pSectorInfo->ubAdminsInBattle = pSectorInfo->ubTroopsInBattle = pSectorInfo->ubElitesInBattle = 0; - } - else - { - UNDERGROUND_SECTORINFO *pSectorInfo; - - pSectorInfo = FindUnderGroundSector( sMapX, sMapY, bMapZ ); - pSectorInfo->ubNumAdmins = pSectorInfo->ubNumTroops = pSectorInfo->ubNumElites = 0; - pSectorInfo->ubAdminsInBattle = pSectorInfo->ubTroopsInBattle = pSectorInfo->ubElitesInBattle = 0; - } + pSectorInfo->ubNumAdmins = pSectorInfo->ubNumTroops = pSectorInfo->ubNumElites = 0; + pSectorInfo->ubAdminsInBattle = pSectorInfo->ubTroopsInBattle = pSectorInfo->ubElitesInBattle = 0; + } + else + { + UNDERGROUND_SECTORINFO *pSectorInfo; + + pSectorInfo = FindUnderGroundSector( sMapX, sMapY, bMapZ ); + pSectorInfo->ubNumAdmins = pSectorInfo->ubNumTroops = pSectorInfo->ubNumElites = 0; + pSectorInfo->ubAdminsInBattle = pSectorInfo->ubTroopsInBattle = pSectorInfo->ubElitesInBattle = 0; + } } - //!!!! //IMPORTANT NEW NOTE: //Whenever returning TRUE, make sure you clear gfBlitBattleSectorLocator; @@ -6383,6 +6424,9 @@ BOOLEAN CheckForEndOfBattle( BOOLEAN fAnEnemyRetreated ) } // Kill all enemies. Sometime even after killing all the enemies, there appeares "in battle" enemies in sector info + // HEADROCK HAM 3.5: This has to take Z-Level into account, otherwise winning a battle underground will kill all + // enemies in the sector above! We still need to run the function though, to prevent confusions with the + // strategic screen, which is what it was doing only for aboveground. UNTIL NOW, muahaha. RemoveStaticEnemiesFromSectorInfo( gWorldSectorX, gWorldSectorY, gbWorldSectorZ ); @@ -7221,15 +7265,20 @@ INT8 CalcSuppressionTolerance( SOLDIERTYPE * pSoldier ) } } - if (bTolerance < gGameExternalOptions.iSuppressionToleranceMin) + // HEADROCK HAM 3.2: This is actually a feature from HAM 2.9. It adds bonuses/penalties for nearby friends. + if (gGameExternalOptions.fFriendliesAffectTolerance) { - bTolerance = gGameExternalOptions.iSuppressionToleranceMin; + bTolerance += CheckStatusNearbyFriendlies( pSoldier ); } - if (bTolerance > gGameExternalOptions.iSuppressionToleranceMax) + // HEADROCK HAM 3.3: Moving rapidly makes one less prone to suppression. + if (gGameExternalOptions.ubTilesMovedPerBonusTolerancePoint > 0) { - bTolerance = gGameExternalOptions.iSuppressionToleranceMax; + bTolerance += pSoldier->bTilesMoved / gGameExternalOptions.ubTilesMovedPerBonusTolerancePoint; } + + bTolerance = __max(bTolerance, gGameExternalOptions.ubSuppressionToleranceMin); + bTolerance = __min(bTolerance, gGameExternalOptions.ubSuppressionToleranceMax); return( bTolerance ); } @@ -7239,163 +7288,154 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) /////////////////////////////////////////////////////////////////////////////// // // HEADROCK HAM B2: This entire function has been completely revamped. + // HEADROCK HAM 3.5: Revamped again. // /////////////////////////////////////////////////////////////////////////////// - // External options. - BOOLEAN APS_SUPPRESSED = gGameExternalOptions.fSuppressionAPLossPerAttack; - BOOLEAN APS_SUPPRESSED_TOTAL = gGameExternalOptions.fSuppressionAPLossPerTurn; - //INT8 SUPPRESSION_AP_LIMIT = gGameExternalOptions.iMinAPLimitFromSuppression; - INT8 MAXIMUM_SUPPRESSION_SHOCK = gGameExternalOptions.iMaxSuppressionShock; - INT8 bTolerance; - INT16 sClosestOpponent, sClosestOppLoc; - UINT8 ubPointsLost, ubTotalPointsLost, ubNewStance; - UINT32 uiLoop; + // This function runs after very attack is completed. It calculates the number + // of "Suppression Points" any character has received during the attack, and + // inflicts various penalties accordingly. + // The most important result of this function is AP loss. + + SOLDIERTYPE * pSoldier; + + INT8 bTolerance; + INT16 sClosestOpponent, sClosestOppLoc; + UINT8 ubPointsLost, ubNewStance; + UINT32 uiLoop; UINT8 ubLoop2; // Flag to determine if the target is cowering (if allowed) - BOOLEAN fCower; - SOLDIERTYPE * pSoldier; + BOOLEAN fCower; + // External options + + // JA2_OPTIONS.INI + INT8 MAXIMUM_SUPPRESSION_SHOCK = gGameExternalOptions.ubMaxSuppressionShock; + + // APBPConstants.INI + UINT16 usLimitSuppressionAPsLostPerAttack = APBPConstants[AP_MAX_SUPPRESSED]; + UINT16 usLimitSuppressionAPsLostPerTurn = APBPConstants[AP_MAX_TURN_SUPPRESSED]; + //HEADROCK HAM 3.5: Ratio between AP Loss and Suppression Shock + UINT16 uiShockPerAPLossDivisor = APBPConstants[AP_SUPPRESSION_SHOCK_DIVISOR]; + + // Loop through every character. for (uiLoop = 0; uiLoop < guiNumMercSlots; uiLoop++) { DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: loop = %d, numslots = %d ",uiLoop, guiNumMercSlots)); pSoldier = MercSlots[uiLoop]; + + // Has this character received any Suppression Points since the last attack? + // HEADROCK: Suppression Points accumulate by bullets flying near the character. It includes + // friendly fire at a certain distance. As of HAM 3.2, it also happens with nearby explosions. + // The number of points accumulated resets to 0 at the end of this function. + if (pSoldier && IS_MERC_BODY_TYPE( pSoldier) && pSoldier->stats.bLife >= OKLIFE && pSoldier->ubSuppressionPoints > 0) { DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: soldier id = %d, life = %d, suppression points = %d",pSoldier->ubID,pSoldier->stats.bLife, pSoldier->ubSuppressionPoints)); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: calc suppression tolerance")); + + // Calculate the character's tolerance to suppression. Helps reduce the severity of the penalties inflicted + // during this function. bTolerance = CalcSuppressionTolerance( pSoldier ); 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 - // 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). + + // This formula gives a linear increase in AP loss relative to Suppression Points. The most Suppression + // Points we have, the most APs we're going to lose. Tolerance mitigates this by making the graph angle + // more shallow. + // The relation between AP Loss and Suppression Points is LINEAR. 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; + // INI-Controlled intensity. SuppressionEffectiveness acts as a percentage applied to the number of lost APs. + // To turn off the entire Suppression system, simply set the INI value to 0. (0% AP Loss) + // The default is obviously 100%. You can increase or decrease it, at will. + // PLEASE NOTE that AP loss governs ALL OTHER SUPPRESSION EFFECTS. + ubPointsLost = ( ubPointsLost * gGameExternalOptions.sSuppressionEffectiveness ) / 100; - // reduce loss of APs based on stance - // ATE: Taken out because we can possibly supress ourselves... - //switch (gAnimControl[ pSoldier->usAnimState ].ubEndHeight) - //{ - // case ANIM_PRONE: - // ubPointsLost = ubPointsLost * 2 / 4; - // break; - // case ANIM_CROUCH: - // ubPointsLost = ubPointsLost * 3 / 4; - // break; - // default: - // break; - //} - - // cap the # of APs we can lose - // HEADROCK HAM B2: This now reads an external value. 0 means no limit. - if (APS_SUPPRESSED == TRUE) + // This is an upper cap for the number of APs we can lose per attack. + if (usLimitSuppressionAPsLostPerAttack > 0) { - if (ubPointsLost > APBPConstants[AP_MAX_SUPPRESSED]) + if (ubPointsLost > usLimitSuppressionAPsLostPerAttack) { - ubPointsLost = (UINT8)APBPConstants[AP_MAX_SUPPRESSED]; + ubPointsLost = __max(255,(UINT8)usLimitSuppressionAPsLostPerAttack); } } - // HEADROCK HAM B2: This makes sure that we never lose more APs than we're allowed per turn, - if (APS_SUPPRESSED_TOTAL = TRUE) + // This makes sure that we never lose more APs than we're allowed per turn. + if (usLimitSuppressionAPsLostPerTurn > 0) { if (pSoldier->ubAPsLostToSuppression + ubPointsLost > APBPConstants[AP_MAX_TURN_SUPPRESSED]) { - ubPointsLost = APBPConstants[AP_MAX_TURN_SUPPRESSED] - pSoldier->ubAPsLostToSuppression; + ubPointsLost = usLimitSuppressionAPsLostPerTurn - pSoldier->ubAPsLostToSuppression; } } - // Keeps a number for later reference - ubTotalPointsLost = ubPointsLost; - // 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")); - - // 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) + // This nifty little bit gives suppression an "extra kick". Soldiers affected by suppression (I.E. lost APs) + // will also suffer from SHOCK. As shock accumulates, the soldier becomes less accurate and may find it + // difficult to perform certain manual tasks. Additionally, he also becomes harder to hit, because the fear + // causes him to hide as best as he can from incoming fire. + // Shock is sliced in half at the start of every turn. Also note that shock may cause "cowering" (see below). + if (gGameExternalOptions.usSuppressionShockEffect > 0) { + // Can't get shock if we haven't lost APs. 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; + // Limit defined by INI. + bShockLimit = MAXIMUM_SUPPRESSION_SHOCK; + // The amount of shock received depends on how many APs we've lost - Every AP lost will cause one + // point of shock. This is then divided by 4 if using the 100AP system. + bShockValue = ubPointsLost / uiShockPerAPLossDivisor; - if (bShockValue < 0) - bShockValue = 0; - if (bShockLimit < 0) - bShockLimit = 0; + bShockValue = __max(0,bShockValue); + bShockLimit = __max(0,bShockLimit); // use external value to determine how effective SHOCK really is. - bShockValue = (bShockValue * gGameExternalOptions.iSuppressionShockEffectiveness) / 100; + bShockValue = (bShockValue * gGameExternalOptions.usSuppressionShockEffect) / 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. + // Make sure total shock doesn't go TOO high. Maximum is around 30, including previous shock + // from suppression and/or wounds. It is possible to breach the maximum after a good suppressive + // attack. if ( pSoldier->aiData.bShock + bShockValue <= bShockLimit ) { // Shock limit not yet breached. Add shock to character. - pSoldier->aiData.bShock += bShockValue; + pSoldier->aiData.bShock = __min(127, 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; + pSoldier->aiData.bShock = __min(127, 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. + // HEADROCK: Cowering is the panic that grips a character due to suffering too much suppression shock. If + // enough shock has been accumulated, the soldier goes into this panic. Generally, cowering will cause + // the character to drop a stance if he can, overriding other conditions for a stance-change (see below). + // Cowering characters may become considerably easier to suppress with additional firepower. In other + // words, if you're cowering, you've effectively turned from a bad-ass to a wimp. fCower = false; - if ( gGameExternalOptions.fSuppressionShock && gGameExternalOptions.iAimPenaltyPerTargetShock > 0 ) + if ( gGameExternalOptions.usSuppressionShockEffect > 0 ) { if (pSoldier->aiData.bShock >= bTolerance) - { fCower = true; } + { + fCower = true; + + // If cowering, increase suppression effectiveness by external percentage. If the setting is + // over 100%, then the condition of cowering makes the character even MORE susceptible to suppression. + if (gGameExternalOptions.usCowerEffectOnSuppression > 0) + ubPointsLost = (ubPointsLost * gGameExternalOptions.usCowerEffectOnSuppression) / 100; + + // If soldier is visible on-screen, report to player that they are cowering. + if ( pSoldier->bVisible != -1 ) + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113HAMMessage[0], pSoldier->name ); + } } - // 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. + // Suppression reduces morale. For every X APs lost, morale goes down by a point. X is defined by INI. + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: check for morale effects")); if (APBPConstants[AP_LOST_PER_MORALE_DROP] > 0 && ubPointsLost > 0) { for ( ubLoop2 = 0; ubLoop2 < (ubPointsLost / APBPConstants[AP_LOST_PER_MORALE_DROP]); ubLoop2++ ) @@ -7408,34 +7448,24 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) ubNewStance = 0; DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: check for reaction")); - // merc may get to react - // Headrock: apply suppression effectiveness percentage to this value. - if ( (pSoldier->ubSuppressionPoints * gGameExternalOptions.iSuppressionEffectiveness) / 100 >= ( 130 / (6 + bTolerance) ) || fCower ) + + // HEADROCK HAM 3.5: Characters who have enough APs to drop WILL DROP. This is strictly for survival reasons, + // because upright characters can easily get themselves killed. Soldiers drop stance for "free", using up + // the APs that they've just lost to do so. + + switch (gAnimControl[ pSoldier->usAnimState ].ubEndHeight) { - // merc gets to use APs to react! - switch (gAnimControl[ pSoldier->usAnimState ].ubEndHeight) - { case ANIM_PRONE: // can't change stance below prone! break; case ANIM_CROUCH: - if (ubTotalPointsLost >= APBPConstants[AP_PRONE] && IsValidStance( pSoldier, ANIM_PRONE ) ) + if (ubPointsLost >= APBPConstants[AP_PRONE] && IsValidStance( pSoldier, ANIM_PRONE ) && gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_PRONE ) { sClosestOpponent = ClosestKnownOpponent( pSoldier, &sClosestOppLoc, NULL ); // HEADROCK: Added cowering. if (sClosestOpponent == NOWHERE || SpacesAway( pSoldier->sGridNo, sClosestOppLoc ) > 8 || fCower) { - if (ubPointsLost < APBPConstants[AP_PRONE]) - { - // Have to give APs back so that we can change stance without - // losing more APs - pSoldier->bActionPoints += (APBPConstants[AP_PRONE] - ubPointsLost); - ubPointsLost = 0; - } - else - { - ubPointsLost -= APBPConstants[AP_PRONE]; - } + ubPointsLost -= APBPConstants[AP_PRONE]; ubNewStance = ANIM_PRONE; } } @@ -7446,7 +7476,7 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) // can't change stance here! break; } - else if (ubTotalPointsLost >= (APBPConstants[AP_CROUCH] + APBPConstants[AP_PRONE]) && ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_PRONE ) && IsValidStance( pSoldier, ANIM_PRONE ) ) + else if (ubPointsLost >= (APBPConstants[AP_CROUCH] + APBPConstants[AP_PRONE]) && ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_PRONE ) && IsValidStance( pSoldier, ANIM_PRONE ) ) { sClosestOpponent = ClosestKnownOpponent( pSoldier, &sClosestOppLoc, NULL ); // HEADROCK: Added cowering. @@ -7469,19 +7499,40 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) ubNewStance = ANIM_CROUCH; } } - else if ( ubTotalPointsLost >= APBPConstants[AP_CROUCH] && ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_CROUCH ) && IsValidStance( pSoldier, ANIM_CROUCH ) ) + else if ( ubPointsLost >= APBPConstants[AP_CROUCH] && ( gAnimControl[ pSoldier->usAnimState ].ubEndHeight != ANIM_CROUCH ) && IsValidStance( pSoldier, ANIM_CROUCH ) ) { // crouch! ubNewStance = ANIM_CROUCH; } break; + } + + + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: reduce action points")); + + // HEADROCK HAM 3.1: If this setting is enabled, it will show an on-screen message that tells us the + // character has lost his entire next turn. This only fires once per turn, the moment a character drops + // to the minimum AP limit. + + if (gGameExternalOptions.fShowSuppressionShutdown) + { + // If we're about the hit the lower limit + if (pSoldier->bActionPoints > APBPConstants[AP_MIN_LIMIT] && pSoldier->bActionPoints - ubPointsLost <= APBPConstants[AP_MIN_LIMIT]) + { + // And soldier is visible + if ( pSoldier->bVisible != -1 ) + { + // "Soldier is pinned down!" + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113HAMMessage[1], pSoldier->name ); + // HEADROCK HAM 3.2: Added a radio locator! + ShowRadioLocator( (UINT8)pSoldier->ubID, SHOW_LOCATOR_NORMAL ); + } } } - DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: reduce action points")); // Reduce action points! // HEADROCK HAM Beta 2.2: Enforce a minimum limit via INI. - if (pSoldier->bActionPoints - ubPointsLost < APBPConstants[AP_MIN_LIMIT] ) + if (pSoldier->bActionPoints - ubPointsLost <= APBPConstants[AP_MIN_LIMIT] ) { pSoldier->bActionPoints = APBPConstants[AP_MIN_LIMIT]; } @@ -7492,7 +7543,7 @@ void HandleSuppressionFire( UINT8 ubTargetedMerc, UINT8 ubCausedAttacker ) // 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; + pSoldier->ubAPsLostToSuppression = __min(255, pSoldier->ubAPsLostToSuppression + ubPointsLost); DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("HandleSuppressionFire: check for quote")); if ( (pSoldier->flags.uiStatusFlags & SOLDIER_PC) && (pSoldier->ubSuppressionPoints > 8) && (pSoldier->ubID == ubTargetedMerc) ) @@ -7554,17 +7605,11 @@ 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; - } + // HEADROCK HAM 3.5: After sufficient testing, suppression clearing now works immediately at the end of + // the attack. ubAPsLostToSuppression is only cleared at the end of the turn, but no longer plays a role + // in affecting the number of APs lost, so it is largely irrelevant now. + + pSoldier->ubSuppressionPoints = 0; } // end of examining one soldier } // end of loop @@ -7617,8 +7662,14 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp if ( (pTarget->bTeam == MILITIA_TEAM) && (pTarget->bSide == gbPlayerNum) ) { - // rebel militia attacked by the player! - MilitiaChangesSides(); + // HEADROCK HAM 3.6: INI setting controls their response + { + if (gGameExternalOptions.ubCanMilitiaBecomeHostile == 2) + { + // rebel militia attacked by the player! + MilitiaChangesSides(); + } + } } // JA2 Gold: fix Slay else if ( (pTarget->bTeam == CIV_TEAM && pTarget->aiData.bNeutral) && pTarget->ubProfile == SLAY && pTarget->stats.bLife >= OKLIFE && CheckFact( 155, 0 ) == FALSE ) @@ -7654,6 +7705,23 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp } } } + else if (pTarget->bTeam == CREATURE_TEAM && pTarget->ubBodyType == BLOODCAT && pTarget->aiData.bNeutral) + { + // Attacked a bloodcat. + MakeBloodcatsHostile(); + // Are bloodcats in this sector affiliated with a faction? + if ( gBloodcatPlacements[SECTOR(pTarget->sSectorX,pTarget->sSectorY)][0].PlacementType == BLOODCAT_PLACEMENT_STATIC && + gBloodcatPlacements[SECTOR(pTarget->sSectorX,pTarget->sSectorY)][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation > NON_CIV_GROUP ) + { + // Temporarily change bloodcat's civilian group + UINT8 ubFaction = pTarget->ubCivilianGroup; + pTarget->ubCivilianGroup = gBloodcatPlacements[SECTOR(pTarget->sSectorX,pTarget->sSectorY)][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation; + // Make entire faction hostile + CivilianGroupMembersChangeSidesWithinProximity( pTarget ); + // Change back + pTarget->ubCivilianGroup = ubFaction; + } + } else { if (pTarget->ubProfile == CARMEN)// Carmen @@ -7692,6 +7760,14 @@ BOOLEAN ProcessImplicationsOfPCAttack( SOLDIERTYPE * pSoldier, SOLDIERTYPE ** pp // Firing at a civ in a civ group who isn't hostile... if anyone in that civ group can see this // going on they should become hostile. CivilianGroupMembersChangeSidesWithinProximity( pTarget ); + + // HEADROCK HAM 3.6: If there are bloodcats affiliated with his group... + if ( gBloodcatPlacements[SECTOR(pTarget->sSectorX,pTarget->sSectorY)][0].PlacementType == BLOODCAT_PLACEMENT_STATIC && + gBloodcatPlacements[SECTOR(pTarget->sSectorX,pTarget->sSectorY)][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation == pTarget->ubCivilianGroup ) + { + // Make them hostile. + MakeBloodcatsHostile(); + } } else if ( pTarget->bTeam == gbPlayerNum && !(gTacticalStatus.uiFlags & INCOMBAT) ) { @@ -8150,17 +8226,6 @@ 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) @@ -8822,3 +8887,79 @@ void RevealAllDroppedEnemyItems() AllSoldiersLookforItems( TRUE ); } + + +// HEADROCK HAM 3.2: This function from HAM 2.9 makes a character look around himself and try to find friends. Dead +// friends lower the result, while more friends (ESP good leaders) will raise the result. The result can then be used +// for any purpose, although this was written specifically to alter a character's suppression tolerance. + +INT8 CheckStatusNearbyFriendlies( SOLDIERTYPE *pSoldier ) +{ + SOLDIERTYPE * pLeader; + UINT8 sModifier = 0; + INT16 usEffectiveLeadership = 0; + UINT16 usEffectiveRangeToLeader = 0; + INT16 usBestLeader = 0; + INT16 usFriendBonus = 0; + + // Run through each friendly. + for ( UINT8 iCounter = gTacticalStatus.Team[ pSoldier->bTeam ].bFirstID ; iCounter <= gTacticalStatus.Team[ pSoldier->bTeam ].bLastID ; iCounter ++ ) + { + pLeader = MercPtrs[ iCounter ]; + // Make sure that character is alive, not too shocked, and conscious, and of higher experience level + // than the character being suppressed. + if (pLeader != pSoldier && pLeader->bActive && pLeader->aiData.bShock < pLeader->stats.bLeadership/5 && + pLeader->stats.bLife >= OKLIFE && pLeader->stats.bExpLevel >= pSoldier->stats.bExpLevel) + { + // Calculate character's leadership and range/3 + usEffectiveLeadership = ((EffectiveLeadership( pLeader ) - 25) / 15); + usEffectiveRangeToLeader = PythSpacesAway( pSoldier->sGridNo, pLeader->sGridNo ) / 3; + // If leader is within range of his leadership stat + if (usEffectiveRangeToLeader <= usEffectiveLeadership+1) + { + // The difference in experience level is important! + usEffectiveLeadership += (pLeader->stats.bExpLevel - pSoldier->stats.bExpLevel); + // Reduce effective leadership with every 3 tiles of distance + usEffectiveLeadership -= usEffectiveRangeToLeader-1; + + // If this is the best leader we've seen so far, + if (usEffectiveLeadership > usBestLeader) + { + // Set this as the best leader + usBestLeader = usEffectiveLeadership; + } + // Friends within range always give at least one tolerance bonus point. + usFriendBonus += 1; + } + } + // Incapacitated or heavily suppressed friends will not be good for our tolerance! + else if (pLeader != pSoldier && pLeader->bActive && (pLeader->aiData.bShock > pSoldier->aiData.bShock || pLeader->stats.bLife <= OKLIFE) ) + { + usEffectiveRangeToLeader = PythSpacesAway( pSoldier->sGridNo, pLeader->sGridNo ); + // If they are no more than 5 tiles away, + if (usEffectiveRangeToLeader <= 5) + { + // Penalty is based on the difference between experience levels, and the range between them, + // and is never less than 1 point. + usEffectiveLeadership = (pLeader->stats.bExpLevel - pSoldier->stats.bExpLevel) / __max(1,(usEffectiveRangeToLeader/2)); + usFriendBonus -= __max(1, usEffectiveLeadership); + } + } + } + + // If we did find someone who's a good enough leader to help us out, + if (usBestLeader > 0) + { + // Add his leadership bonus, minus the point we got for him before. He'll give at least one + // point, like anybody else. + usFriendBonus += __max(usBestLeader-1, 1); + } + + // Add no more than five points for nearby friends. + usFriendBonus = __min(usFriendBonus, 5); + usFriendBonus = __max(usFriendBonus, -5); + sModifier += usFriendBonus; + + return(sModifier); + +} \ No newline at end of file diff --git a/Tactical/Overhead.h b/Tactical/Overhead.h index fa1c116a..b15b9a7d 100644 --- a/Tactical/Overhead.h +++ b/Tactical/Overhead.h @@ -318,6 +318,8 @@ BOOLEAN HandleGotoNewGridNo( SOLDIERTYPE *pSoldier, BOOLEAN *pfKeepMoving, BOOLE SOLDIERTYPE * ReduceAttackBusyCount( ); // HEADROCK HAM B2.6: Made this public so it can be used elsewhere. INT8 CalcSuppressionTolerance( SOLDIERTYPE * pSoldier ); +// HEADROCK HAM 3.2: A new function for checking the condition of nearby friendlies and returning a modifier. +INT8 CheckStatusNearbyFriendlies( SOLDIERTYPE *pSoldier ); void CommonEnterCombatModeCode( ); @@ -374,4 +376,6 @@ extern BOOLEAN gogglewarning; // will a sam site under the players control shoot down an airraid? BOOLEAN WillAirRaidBeStopped( INT16 sSectorX, INT16 sSectorY ); +// HEADROCK HAM 3.5: Externalized for First Arrival enemy check +extern UINT8 NumEnemyInSector(); #endif \ No newline at end of file diff --git a/Tactical/PATHAI.cpp b/Tactical/PATHAI.cpp index 61feaa34..f9b6e352 100644 --- a/Tactical/PATHAI.cpp +++ b/Tactical/PATHAI.cpp @@ -3358,7 +3358,7 @@ INT32 FindBestPath(SOLDIERTYPE *s , INT16 sDestination, INT8 ubLevel, INT16 usMo case RUNNING: case ADULTMONSTER_WALKING: // save on casting - if((UsingNewInventorySystem() == true) && s->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND ) //ubAPCost = ubAPCost * 10 / ( (UINT8) (RUNDIVISORBPACK * 10)); ubAPCost = ubAPCost + APBPConstants[AP_MODIFIER_RUN] + APBPConstants[AP_MODIFIER_PACK]; else @@ -3368,19 +3368,19 @@ INT32 FindBestPath(SOLDIERTYPE *s , INT16 sDestination, INT8 ubLevel, INT16 usMo break; case WALKING: case ROBOT_WALK: - if((UsingNewInventorySystem() == true) && s->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND ) ubAPCost = (ubAPCost + APBPConstants[AP_MODIFIER_WALK] + APBPConstants[AP_MODIFIER_PACK]); //WALKCOSTBPACK); else ubAPCost = (ubAPCost + APBPConstants[AP_MODIFIER_WALK]); //WALKCOST); break; case SWATTING: - if((UsingNewInventorySystem() == true) && s->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND ) ubAPCost = (ubAPCost + APBPConstants[AP_MODIFIER_SWAT] + APBPConstants[AP_MODIFIER_PACK]); //SWATCOSTBPACK); else ubAPCost = (ubAPCost + APBPConstants[AP_MODIFIER_SWAT]); //SWATCOST); break; case CRAWLING: - if((UsingNewInventorySystem() == true) && s->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( s ) != ITEM_NOT_FOUND ) ubAPCost = (ubAPCost + APBPConstants[AP_MODIFIER_CRAWL] + APBPConstants[AP_MODIFIER_PACK]); //CRAWLCOSTBPACK); else ubAPCost = (ubAPCost + APBPConstants[AP_MODIFIER_CRAWL]); //CRAWLCOST); @@ -4308,27 +4308,27 @@ INT16 PlotPath( SOLDIERTYPE *pSold, INT16 sDestGridno, INT8 bCopyRoute, INT8 bPl { case RUNNING: sPoints += sTileCost + sExtraCostStand + APBPConstants[AP_MODIFIER_RUN]; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; case WALKING : sPoints += sTileCost + sExtraCostStand + APBPConstants[AP_MODIFIER_WALK]; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; case SWATTING: sPoints += sTileCost + sExtraCostStand + APBPConstants[AP_MODIFIER_SWAT]; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; case CRAWLING: sPoints += sTileCost + sExtraCostStand + APBPConstants[AP_MODIFIER_CRAWL]; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; default : sPoints += sPoints + sTileCost; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; } @@ -4344,22 +4344,22 @@ INT16 PlotPath( SOLDIERTYPE *pSold, INT16 sDestGridno, INT8 bCopyRoute, INT8 bPl // CHRISL: Adjusted system to use different move costs while wearing a backpack // store WALK cost sPointsWalk += sTileCost + APBPConstants[AP_MODIFIER_WALK] + sExtraCostStand; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPointsWalk += APBPConstants[AP_MODIFIER_PACK]; // now get cost as if CRAWLING sPointsCrawl += sTileCost + APBPConstants[AP_MODIFIER_CRAWL] + sExtraCostCrawl; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPointsCrawl += APBPConstants[AP_MODIFIER_PACK]; // now get cost as if SWATTING sPointsSwat += sTileCost + APBPConstants[AP_MODIFIER_SWAT] + sExtraCostSwat; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPointsSwat += APBPConstants[AP_MODIFIER_PACK]; // now get cost as if RUNNING sPointsRun += sTileCost + APBPConstants[AP_MODIFIER_RUN] + sExtraCostStand; - if((UsingNewInventorySystem() == true) && pSold->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSold ) != ITEM_NOT_FOUND ) sPointsRun += APBPConstants[AP_MODIFIER_PACK]; } diff --git a/Tactical/Points.cpp b/Tactical/Points.cpp index 7c842136..efa673f0 100644 --- a/Tactical/Points.cpp +++ b/Tactical/Points.cpp @@ -337,7 +337,7 @@ INT16 ActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, UINT16 u case BLOODCAT_RUN: // CHRISL sPoints = sTileCost + APBPConstants[AP_MODIFIER_RUN]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; @@ -351,7 +351,7 @@ INT16 ActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, UINT16 u case WALKING : // CHRISL sPoints = sTileCost + APBPConstants[AP_MODIFIER_WALK]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; @@ -360,12 +360,12 @@ INT16 ActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, UINT16 u // CHRISL case SWATTING: sPoints = sTileCost + APBPConstants[AP_MODIFIER_SWAT]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; case CRAWLING: sPoints = sTileCost + APBPConstants[AP_MODIFIER_CRAWL]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; @@ -423,7 +423,7 @@ INT16 EstimateActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, case BLOODCAT_RUN: // CHRISL sPoints = sTileCost + APBPConstants[AP_MODIFIER_RUN]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; @@ -437,7 +437,7 @@ INT16 EstimateActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, // CHRISL case WALKING : sPoints = sTileCost + APBPConstants[AP_MODIFIER_WALK]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; @@ -446,12 +446,12 @@ INT16 EstimateActionPointCost( SOLDIERTYPE *pSoldier, INT16 sGridNo, INT8 bDir, // CHRISL case SWATTING: sPoints = sTileCost + APBPConstants[AP_MODIFIER_SWAT]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; case CRAWLING: sPoints = sTileCost + APBPConstants[AP_MODIFIER_CRAWL]; - if((UsingNewInventorySystem() == true) && pSoldier->inv[BPACKPOCKPOS].exists() == true) + if((UsingNewInventorySystem() == true) && FindBackpackOnSoldier( pSoldier ) != ITEM_NOT_FOUND ) sPoints += APBPConstants[AP_MODIFIER_PACK]; break; @@ -668,9 +668,11 @@ void DeductPoints( SOLDIERTYPE *pSoldier, INT16 sAPCost, INT32 iBPCost,BOOLEAN f pSoldier->bActionPoints -= (INT16)( (float)( pSoldier->sBreathRed + iBPCost - BREATH_RED_MAX ) / (float)( 5 * APBPConstants[BP_RATIO_RED_PTS_TO_NORMAL]) * (float)(4 * (float)APBPConstants[AP_MAXIMUM] / 100)); - if ( pSoldier->bActionPoints < 0 ) + // HEADROCK HAM 3.1: This may be the problem with suppression - it limits the lower APs to 0, which breaks + // suppression's negative values. Changed instances of "0" to the APBP constant. + if ( pSoldier->bActionPoints < APBPConstants[AP_MIN_LIMIT] ) { - pSoldier->bActionPoints = 0; + pSoldier->bActionPoints = APBPConstants[AP_MIN_LIMIT]; } pSoldier->sBreathRed = BREATH_RED_MAX; @@ -1130,46 +1132,63 @@ INT16 CalcTotalAPsToAttack( SOLDIERTYPE *pSoldier, INT16 sGridNo, UINT8 ubAddTur if (gGameExternalOptions.fIncreasedAimingCost ) { // HEADROCK HAM B2.6: Changed the number of APs to attack when aiming. + // HEADROCK HAM 3.1: Externalized the entire function to allow customization of each detail. if (bAimTime > 0) { GetAPChargeForShootOrStabWRTGunRaises( pSoldier, sGridNo, ubAddTurningCost, &fAddingTurningCost, &fAddingRaiseGunCost ); if(fAddingRaiseGunCost == TRUE) { - //CHRISL: I'm disabling this for now. Charging 1/2 ready cost for every shot isn't legitimate - // but for the time being, we have no way to track whether we've previously aimed or not. Until - // we do, this code shouldn't be used. - // 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; + // HEADROCK HAM 3: No idea what should come here... For now I've put my extra gun-raise costs + // outside this IF (see below). + } + + // HEADROCK HAM 3: One-time penalty: Add part of the weapon's Ready AP cost. Reinstated because + // it's now externalized. + if (gGameExternalOptions.ubFirstAimReadyCostDivisor > 0) + { + UINT16 usWeaponReadyTime; + UINT8 ubReadyTimeDivisor; + + usWeaponReadyTime = Weapon[ usItemNum ].ubReadyTime * (100 - GetPercentReadyTimeAPReduction(&pSoldier->inv[HANDPOS])) / 100; + ubReadyTimeDivisor = gGameExternalOptions.ubFirstAimReadyCostDivisor; + sAPCost += usWeaponReadyTime / ubReadyTimeDivisor; } - // Add regular aim time for the first 4 aiming actions. - sAPCost += __min((bAimTime*APBPConstants[AP_CLICK_AIM]),(4*APBPConstants[AP_CLICK_AIM])); - // 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) - { - //CHRISL: I'm disabling this for now. Charging an extra "click" every time we use a scope - // isn't legitimate. If we could track whether we previously used a scope on the current - // target, that would be another matter. But for now, we can't, so this should be used. - //sAPCost += APBPConstants[AP_CLICK_AIM]; - } - // Add 2 APs for each aiming point between 5 and 6 + // Add an individual cost for EACH click, as necessary. + + sAPCost += APBPConstants[AP_FIRST_CLICK_AIM_SCOPE]; + + if (bAimTime > 1) + sAPCost += APBPConstants[AP_SECOND_CLICK_AIM_SCOPE]; + if (bAimTime > 2) + sAPCost += APBPConstants[AP_THIRD_CLICK_AIM_SCOPE]; + if (bAimTime > 3) + sAPCost += APBPConstants[AP_FOURTH_CLICK_AIM_SCOPE]; if (bAimTime > 4) - { - sAPCost += __min(((bAimTime - 4) * APBPConstants[AP_CLICK_AIM] * 2), (4*APBPConstants[AP_CLICK_AIM])); - } - // Add 3 APs for each aiming point beyond 6. + sAPCost += APBPConstants[AP_FIFTH_CLICK_AIM_SCOPE]; + if (bAimTime > 5) + sAPCost += APBPConstants[AP_SIXTH_CLICK_AIM_SCOPE]; if (bAimTime > 6) - { - sAPCost += (bAimTime - 6) * APBPConstants[AP_CLICK_AIM] * 3; - } + sAPCost += APBPConstants[AP_SEVENTH_CLICK_AIM_SCOPE]; + if (bAimTime > 7) + sAPCost += APBPConstants[AP_EIGHTTH_CLICK_AIM_SCOPE]; } + + // Weapon has no scope or not within requried range. Apply regular AP costs. + else + { + sAPCost += bAimTime * APBPConstants[AP_CLICK_AIM]; + } + } } + + // Regular cost system else { sAPCost += bAimTime * APBPConstants[AP_CLICK_AIM]; @@ -1402,7 +1421,6 @@ INT16 BaseAPsToShootOrStab( INT16 bAPs, INT16 bAimSkill, OBJECTTYPE * pObj ) //{ // Top *= 100; //} - // WANNE : Fixed CTD that occurs when trowing item (grenade, throwing knife, ...) // with open description box in tactical INT16 baseAPsToShootOrStab = -1; diff --git a/Tactical/Rotting Corpses.h b/Tactical/Rotting Corpses.h index d071f40e..b8d3fac1 100644 --- a/Tactical/Rotting Corpses.h +++ b/Tactical/Rotting Corpses.h @@ -169,7 +169,8 @@ void HandleCrowLeave( SOLDIERTYPE *pSoldier ); void HandleCrowFlyAway( SOLDIERTYPE *pSoldier ); // WDS - increase number of corpses -#define MAX_ROTTING_CORPSES 250 +// HEADROCK HAM 3.6: Increase again (250->500). +#define MAX_ROTTING_CORPSES 500 //extern ROTTING_CORPSE gRottingCorpse[ MAX_ROTTING_CORPSES ]; extern std::vector gRottingCorpse; diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index d177a789..6944464e 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -959,6 +959,7 @@ SOLDIERTYPE& SOLDIERTYPE::operator=(const OLDSOLDIERTYPE_101& src) this->snowCamo = src.snowCamo; this->wornSnowCamo = src.wornSnowCamo; + } return *this; } @@ -1794,6 +1795,9 @@ void HandleCrowShadowNewPosition( SOLDIERTYPE *pSoldier ) extern INT16 DynamicAdjustAPConstants(INT16 iniReadValue, INT16 iniDefaultValue, BOOLEAN reverse); +// This function calculates how many APs are added to a character's pool at the start of the round. The normal maximum +// amount that can be added is 80% of the Maximum AP value, with the remaining 20% coming from APs reserved in the +// previous round. See also CalcNewActionPoints() INT16 SOLDIERTYPE::CalcActionPoints( void ) { INT16 ubPoints,ubMaxAPs; @@ -1850,8 +1854,11 @@ INT16 SOLDIERTYPE::CalcActionPoints( void ) // If resulting APs are below our permitted minimum, raise them to it! // HEADROCK: Enforce new minimums due to suppression. I should've done this neater though. - if (ubPoints < APBPConstants[AP_MIN_LIMIT]) - ubPoints = APBPConstants[AP_MIN_LIMIT]; + // HEADROCK HAM 3.6: This was the wrong place to put AP_MIN_LIMIT. The value here should be AP_MINIMUM, which is + // the minimum amount of APs a character can GAIN each turn on top of what he had last turn. AP_MIN_LIMIT has been + // moved to CalcNewActionPoints() where it belongs. + if (ubPoints < APBPConstants[AP_MINIMUM]) + ubPoints = APBPConstants[AP_MINIMUM]; // make sure action points doesn't exceed the permitted maximum ubMaxAPs = gubMaxActionPoints[ this->ubBodyType ]; @@ -1975,6 +1982,10 @@ void SOLDIERTYPE::CalcNewActionPoints( void ) } this->bActionPoints += this->CalcActionPoints( ); + // HEADROCK HAM 3.6: This should've been here all along. This enforces an absolute minimum limit on APs, which + // can be negative. + if (this->bActionPoints < APBPConstants[AP_MIN_LIMIT]) + this->bActionPoints = APBPConstants[AP_MIN_LIMIT]; // Don't max out if we are drugged.... if ( !GetDrugEffect( this, DRUG_TYPE_ADRENALINE ) ) @@ -5511,7 +5522,9 @@ void SoldierGotHitGunFire( SOLDIERTYPE *pSoldier, UINT16 usWeaponIndex, INT16 sD { if ( gGameSettings.fOptions[ TOPTION_BLOOD_N_GORE ] ) { - if (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && usWeaponIndex == BARRETT )) + // HEADROCK HAM 3.6: Reattached "Max Distance For Messy Death" tag from the XML! God knows why it wasn't attached when they MADE THAT TAG. + //if (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= Weapon[usWeaponIndex].maxdistformessydeath || (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && usWeaponIndex == BARRETT )) + if (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= Weapon[usWeaponIndex].maxdistformessydeath) { sNewGridNo = NewGridNo( (INT16)pSoldier->sGridNo, (INT8)( DirectionInc( pSoldier->ubDirection ) ) ); @@ -5532,7 +5545,9 @@ void SoldierGotHitGunFire( SOLDIERTYPE *pSoldier, UINT16 usWeaponIndex, INT16 sD { if ( gGameSettings.fOptions[ TOPTION_BLOOD_N_GORE ] ) { - if (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && usWeaponIndex == BARRETT )) + // HEADROCK HAM 3.6: Reattached "Max Distance For Messy Death" tag from the XML! God knows why it wasn't attached when they MADE THAT TAG. + //if (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= Weapon[usWeaponIndex].maxdistformessydeath || (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && usWeaponIndex == BARRETT )) + if (SpacesAway( pSoldier->sGridNo, Menptr[ubAttackerID].sGridNo ) <= Weapon[usWeaponIndex].maxdistformessydeath) { // possibly play torso explosion anim! @@ -5581,6 +5596,11 @@ void SoldierGotHitGunFire( SOLDIERTYPE *pSoldier, UINT16 usWeaponIndex, INT16 sD if ( fFallenOver ) { + // HEADROCK HAM 3.2: Critical legshots cost an extra number of APs, based on shot damage. + if (gGameExternalOptions.fCriticalLegshotCausesAPLoss) + { + DeductPoints( pSoldier, APBPConstants[AP_LOSS_PER_LEGSHOT_DAMAGE]*sDamage, 0); + } SoldierCollapse( pSoldier ); return; } @@ -6613,25 +6633,24 @@ void SOLDIERTYPE::EVENT_BeginMercTurn( BOOLEAN fFromRealTime, INT32 iRealTimeCou this->CalcNewActionPoints( ); // HEADROCK HAM 3.6: If this soldier is in a "moving" animation, but has not moved any tiles - // in the previous turn, then the player has apparently forgotten that he was moving. - // In this case, abort the character's action. - - // If hasn't moved since the start of last round - // AND this function is being executed in Turn Based mode - // AND character is a player-controlled merc - if (!fFromRealTime && !this->bTilesMoved && this->bTeam == OUR_TEAM ) - { - // but are doing a movement animation - if ( !( gAnimControl[ this->usAnimState ].uiFlags & ANIM_STATIONARY ) ) - { - // Stop the merc - this->EVENT_StopMerc( this->sGridNo, this->ubDirection ); - this->pathing.sFinalDestination = NOWHERE; - } + // in the previous turn, then the player has apparently forgotten that he was moving. + // In this case, abort the character's action. - // Reset destination - //this->pathing.sFinalDestination = this->sGridNo; - } + // If hasn't moved since the start of last round + // AND this function is being executed in Turn Based mode + // AND character is a player-controlled merc + if (!fFromRealTime && !this->bTilesMoved && this->bTeam == OUR_TEAM ) + { + // but is doing a movement animation + if ( !( gAnimControl[ this->usAnimState ].uiFlags & ANIM_STATIONARY ) ) + { + // Stop the merc + this->EVENT_StopMerc( this->sGridNo, this->ubDirection ); + // Reset destination + //this->pathing.sFinalDestination = this->sGridNo; + this->pathing.sFinalDestination = NOWHERE; + } + } this->bTilesMoved = 0; @@ -6711,15 +6730,11 @@ 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; - } + + // HEADROCK HAM 3.5: After considerable testing, suppression is now cleared after every attack. Total APs lost + // is cleared every turn (here) and only acts as reference now (no effect on AP loss). + this->ubAPsLostToSuppression = 0; + this->flags.fCloseCall = FALSE; this->ubMovementNoiseHeard = 0; diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index 8c485f25..6b3b7f49 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -277,6 +277,28 @@ enum #define SOLDIER_CLASS_ENEMY( bSoldierClass ) ( ( bSoldierClass >= SOLDIER_CLASS_ADMINISTRATOR ) && ( bSoldierClass <= SOLDIER_CLASS_ARMY ) ) #define SOLDIER_CLASS_MILITIA( bSoldierClass ) ( ( bSoldierClass >= SOLDIER_CLASS_GREEN_MILITIA ) && ( bSoldierClass <= SOLDIER_CLASS_ELITE_MILITIA ) ) +// Types of uniforms available +enum +{ + UNIFORM_ENEMY_ADMIN = 0, + UNIFORM_ENEMY_TROOP, + UNIFORM_ENEMY_ELITE, + UNIFORM_MILITIA_ROOKIE, + UNIFORM_MILITIA_REGULAR, + UNIFORM_MILITIA_ELITE, + NUM_UNIFORMS, +}; + +// enum of uniform pieces +typedef struct +{ + PaletteRepID vest; + PaletteRepID pants; +}UNIFORMCOLORS; + +// HEADROCK HAM 3.6: Uniform colors for the different soldier classes +extern UNIFORMCOLORS gUniformColors[NUM_UNIFORMS]; + // This macro should be used whenever we want to see if someone is neutral // IF WE ARE CONSIDERING ATTACKING THEM. Creatures & bloodcats will attack neutrals // but they can't attack empty vehicles!! @@ -1021,7 +1043,6 @@ public: UINT32 uiTimeSoldierWillArrive; - INT8 bVehicleUnderRepairID; INT32 iTimeCanSignElsewhere; INT8 bHospitalPriceModifier; @@ -1050,7 +1071,11 @@ public: INT8 snowCamo; INT8 wornSnowCamo; - INT16 filler; + // HEADROCK HAM 3.6: Added integer tracking the facility this character is using. + INT16 sFacilityTypeOperated; + // HEADROCK HAM 3.6: I'm removing this filler to make room for the above variable. I'm very worried though, + // I don't know if this is a good idea at all... + //INT16 filler; char endOfPOD; // marker for end of POD (plain old data) diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index 79da8dbe..6f531281 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -667,7 +667,17 @@ SOLDIERTYPE* TacticalCreateSoldier( SOLDIERCREATE_STRUCT *pCreateStruct, UINT8 * { if ( gTacticalStatus.fCivGroupHostile[ Soldier.ubCivilianGroup ] == CIV_GROUP_HOSTILE ) { - Soldier.aiData.bNeutral = FALSE; + // HEADROCK HAM 3.6: Flag to prevent non-combat civilians from becoming hostile and forcing + // you to kill them... + if (!gGameExternalOptions.fCanTrueCiviliansBecomeHostile && + (Soldier.ubBodyType >= FATCIV && Soldier.ubBodyType <= CRIPPLECIV )) + { + Soldier.aiData.bNeutral = TRUE; + } + else + { + Soldier.aiData.bNeutral = FALSE; + } } else { @@ -1211,34 +1221,47 @@ void GeneratePaletteForSoldier( SOLDIERTYPE *pSoldier, UINT8 ubSoldierClass ) // OK, After skin, hair we could have a forced color scheme.. use here if so switch( ubSoldierClass ) { + // HEADROCK HAM 3.6: Now reads default colors from XML. case SOLDIER_CLASS_ADMINISTRATOR: - SET_PALETTEREP_ID( pSoldier->VestPal, "YELLOWVEST" ); - SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" ); + //SET_PALETTEREP_ID( pSoldier->VestPal, "YELLOWVEST" ); + //SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" ); + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_ENEMY_ADMIN ].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_ENEMY_ADMIN ].pants ); pSoldier->ubSoldierClass = ubSoldierClass; return; case SOLDIER_CLASS_ELITE: - SET_PALETTEREP_ID( pSoldier->VestPal, "BLACKSHIRT" ); - SET_PALETTEREP_ID( pSoldier->PantsPal, "BLACKPANTS" ); + //SET_PALETTEREP_ID( pSoldier->VestPal, "BLACKSHIRT" ); + //SET_PALETTEREP_ID( pSoldier->PantsPal, "BLACKPANTS" ); + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_ENEMY_ELITE ].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_ENEMY_ELITE ].pants ); pSoldier->ubSoldierClass = ubSoldierClass; return; case SOLDIER_CLASS_ARMY: - SET_PALETTEREP_ID( pSoldier->VestPal, "REDVEST" ); - SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" ); + //SET_PALETTEREP_ID( pSoldier->VestPal, "REDVEST" ); + //SET_PALETTEREP_ID( pSoldier->PantsPal, "GREENPANTS" ); + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_ENEMY_TROOP ].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_ENEMY_TROOP ].pants ); pSoldier->ubSoldierClass = ubSoldierClass; return; case SOLDIER_CLASS_GREEN_MILITIA: - SET_PALETTEREP_ID( pSoldier->VestPal, "GREENVEST" ); - SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); + //SET_PALETTEREP_ID( pSoldier->VestPal, "GREENVEST" ); + //SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_MILITIA_ROOKIE ].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_MILITIA_ROOKIE ].pants ); pSoldier->ubSoldierClass = ubSoldierClass; return; case SOLDIER_CLASS_REG_MILITIA: - SET_PALETTEREP_ID( pSoldier->VestPal, "JEANVEST" ); - SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); + //SET_PALETTEREP_ID( pSoldier->VestPal, "JEANVEST" ); + //SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_MILITIA_REGULAR ].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_MILITIA_REGULAR ].pants ); pSoldier->ubSoldierClass = ubSoldierClass; return; case SOLDIER_CLASS_ELITE_MILITIA: - SET_PALETTEREP_ID( pSoldier->VestPal, "BLUEVEST" ); - SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); + //SET_PALETTEREP_ID( pSoldier->VestPal, "BLUEVEST" ); + //SET_PALETTEREP_ID( pSoldier->PantsPal, "BEIGEPANTS" ); + SET_PALETTEREP_ID( pSoldier->VestPal, gUniformColors[ UNIFORM_MILITIA_ELITE ].vest ); + SET_PALETTEREP_ID( pSoldier->PantsPal, gUniformColors[ UNIFORM_MILITIA_ELITE ].pants ); pSoldier->ubSoldierClass = ubSoldierClass; return; case SOLDIER_CLASS_MINER: @@ -1561,6 +1584,7 @@ void InitSoldierStruct( SOLDIERTYPE *pSoldier ) pSoldier->uiXRayActivatedTime = 0; pSoldier->bBulletsLeft = 0; pSoldier->bVehicleUnderRepairID = -1; + pSoldier->sFacilityTypeOperated = -1; // HEADROCK HAM 3.6: Facility Operated } @@ -1973,7 +1997,9 @@ void CreateDetailedPlacementGivenBasicPlacementInfo( SOLDIERCREATE_STRUCT *pp, B case BLOODCAT: pp->bExpLevel = 5 + bExpLevelModifier; - if( SECTOR( gWorldSectorX, gWorldSectorY ) == SEC_I16 ) + // HEADROCK HAM 3.6: There can be several lairs now. Find out if this one is. + UINT8 PlacementType = gBloodcatPlacements[ SECTOR(gWorldSectorX, gWorldSectorY) ][0].PlacementType; + if( PlacementType == BLOODCAT_PLACEMENT_LAIR ) { pp->bExpLevel += gGameOptions.ubDifficultyLevel; } diff --git a/Tactical/Soldier Init List.cpp b/Tactical/Soldier Init List.cpp index 54b6fe43..be7d1df3 100644 --- a/Tactical/Soldier Init List.cpp +++ b/Tactical/Soldier Init List.cpp @@ -1915,8 +1915,7 @@ void AddSoldierInitListBloodcats() { //This map has no bloodcat placements, so don't waste CPU time. return; } - - if( pSector->bBloodCatPlacements ) + else { //We don't yet know the number of bloodcat placements in this sector so //count them now, and permanently record it. INT8 bBloodCatPlacements = 0; @@ -1929,7 +1928,18 @@ void AddSoldierInitListBloodcats() } curr = curr->next; } - if( bBloodCatPlacements != pSector->bBloodCatPlacements && + // No placements on the map itself? + if( !bBloodCatPlacements ) + { + // Don't place! + return; + } + // HEADROCK HAM 3.6: Check has been changed completely. We now use whichever value is lower - the ones we've + // set from XML, or the ones existing on the map. Either could override the other, if it is lower. + pSector->bBloodCatPlacements = __min(bBloodCatPlacements, pSector->bBloodCatPlacements); + pSector->bBloodCats = __min(pSector->bBloodCats, pSector->bBloodCatPlacements); + + /*if( bBloodCatPlacements != pSector->bBloodCatPlacements && ubSectorID != SEC_I16 && ubSectorID != SEC_N5 ) { #ifdef JA2BETAVERSION @@ -1938,18 +1948,21 @@ void AddSoldierInitListBloodcats() pSector->bBloodCatPlacements, gWorldSectorY + 'A' - 1, gWorldSectorX, bBloodCatPlacements ); DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, NULL ); #endif - // WANNE: Fix by Headrock + // HEADROCK HAM 3.5: This "solution" is extremely silly, as it prevents legal placement of bloodcats + // on the map if any discrepancy is encountered, which limits modders severely. Also, because the + // pSector->bBloodCatPlacements value is hardcoded, there is virtually no way for modders to increase + // the number of bloodcats on their own. //pSector->bBloodCatPlacements = bBloodCatPlacements; //pSector->bBloodCats = -1; - + // A better solution is to limit the number of bloodcats on the map based on whichever is lower - the + // hardcode, or the map-read value. pSector->bBloodCatPlacements = __min(bBloodCatPlacements, pSector->bBloodCatPlacements); pSector->bBloodCats = __min(pSector->bBloodCats, pSector->bBloodCatPlacements); - if( !bBloodCatPlacements ) { return; } - } + }*/ } if( pSector->bBloodCats > 0 ) { //Add them to the world now... @@ -2565,5 +2578,11 @@ void AddSoldierInitListMilitiaOnEdge( UINT8 ubStrategicInsertionCode, UINT8 ubNu } UpdateMercInSector( pSoldier, gWorldSectorX, gWorldSectorY, 0 ); } + + // HEADROCK HAM 3.2: Experimental, militia reinforcements arrive with 0 APs. + if (gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 1 || gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 3) + { + pSoldier->bActionPoints = 0; + } } } diff --git a/Tactical/Soldier Profile.cpp b/Tactical/Soldier Profile.cpp index e88099eb..eef0766e 100644 --- a/Tactical/Soldier Profile.cpp +++ b/Tactical/Soldier Profile.cpp @@ -447,6 +447,13 @@ BOOLEAN LoadMercProfiles(void) gMercProfiles[ uiLoop ].bHatedCount[1] = gMercProfiles[ uiLoop ].bHatedTime[1]; gMercProfiles[ uiLoop ].bLearnToHateCount = gMercProfiles[ uiLoop ].bLearnToHateTime; gMercProfiles[ uiLoop ].bLearnToLikeCount = gMercProfiles[ uiLoop ].bLearnToLikeTime; + + if (gGameExternalOptions.fReadProfileDataFromXML) + { + // HEADROCK PROFEX: Overwrite data read from PROF.DAT with data read from XML + OverwriteMercProfileWithXMLData( uiLoop ); + OverwriteMercOpinionsWithXMLData( uiLoop ); + } } // SET SOME DEFAULT LOCATIONS FOR STARTING NPCS @@ -1664,3 +1671,96 @@ BOOLEAN IsProfileIdAnAimOrMERCMerc( UINT8 ubProfileID ) return( FALSE ); } + +void OverwriteMercProfileWithXMLData( UINT32 uiLoop ) +{ + ////////////////////////////////////////////////////////////////////////////////// + // + // HEADROCK PROFEX: Profile Externalization + // + // This is a complete hack which is meant for temporary use until a better system + // can be implemented. This bit OVERWRITES data accumulated so far, by drawing + // new data from XML. This allows making PROEDIT obsolete. + // + ////////////////////////////////////////////////////////////////////////////////// + wcscpy(gMercProfiles[ uiLoop ].zName, tempProfiles[ uiLoop ].zName) ; + wcscpy(gMercProfiles[ uiLoop ].zNickname, tempProfiles[ uiLoop ].zNickname) ; + + gMercProfiles[ uiLoop ].ubFaceIndex = tempProfiles[ uiLoop ].ubFaceIndex ; + gMercProfiles[ uiLoop ].usEyesX = tempProfiles[ uiLoop ].usEyesX ; + gMercProfiles[ uiLoop ].usEyesY = tempProfiles[ uiLoop ].usEyesY ; + gMercProfiles[ uiLoop ].usMouthX = tempProfiles[ uiLoop ].usMouthX ; + gMercProfiles[ uiLoop ].usMouthY = tempProfiles[ uiLoop ].usMouthY ; + gMercProfiles[ uiLoop ].uiEyeDelay = tempProfiles[ uiLoop ].uiEyeDelay ; + gMercProfiles[ uiLoop ].uiMouthDelay = tempProfiles[ uiLoop ].uiMouthDelay ; + gMercProfiles[ uiLoop ].uiBlinkFrequency = tempProfiles[ uiLoop ].uiBlinkFrequency ; + gMercProfiles[ uiLoop ].uiExpressionFrequency = tempProfiles[ uiLoop ].uiExpressionFrequency ; + + strcpy(gMercProfiles[ uiLoop ].PANTS, tempProfiles[ uiLoop ].PANTS) ; + strcpy(gMercProfiles[ uiLoop ].VEST, tempProfiles[ uiLoop ].VEST) ; + strcpy(gMercProfiles[ uiLoop ].SKIN, tempProfiles[ uiLoop ].SKIN) ; + strcpy(gMercProfiles[ uiLoop ].HAIR, tempProfiles[ uiLoop ].HAIR) ; + + gMercProfiles[ uiLoop ].bSex = tempProfiles[ uiLoop ].bSex ; + gMercProfiles[ uiLoop ].ubBodyType = tempProfiles[ uiLoop ].ubBodyType ; + gMercProfiles[ uiLoop ].uiBodyTypeSubFlags = tempProfiles[ uiLoop ].uiBodyTypeSubFlags ; + + gMercProfiles[ uiLoop ].bAttitude = tempProfiles[ uiLoop ].bAttitude ; + gMercProfiles[ uiLoop ].bPersonalityTrait = tempProfiles[ uiLoop ].bPersonalityTrait ; + gMercProfiles[ uiLoop ].ubNeedForSleep = tempProfiles[ uiLoop ].ubNeedForSleep ; + + gMercProfiles[ uiLoop ].bReputationTolerance = tempProfiles[ uiLoop ].bReputationTolerance ; + gMercProfiles[ uiLoop ].bDeathRate = tempProfiles[ uiLoop ].bDeathRate ; + + gMercProfiles[ uiLoop ].bLifeMax = tempProfiles[ uiLoop ].bLifeMax ; + gMercProfiles[ uiLoop ].bLife = tempProfiles[ uiLoop ].bLife ; + gMercProfiles[ uiLoop ].bStrength = tempProfiles[ uiLoop ].bStrength ; + gMercProfiles[ uiLoop ].bAgility = tempProfiles[ uiLoop ].bAgility ; + gMercProfiles[ uiLoop ].bDexterity = tempProfiles[ uiLoop ].bDexterity ; + gMercProfiles[ uiLoop ].bWisdom = tempProfiles[ uiLoop ].bWisdom ; + gMercProfiles[ uiLoop ].bMarksmanship = tempProfiles[ uiLoop ].bMarksmanship ; + gMercProfiles[ uiLoop ].bExplosive = tempProfiles[ uiLoop ].bExplosive ; + gMercProfiles[ uiLoop ].bLeadership = tempProfiles[ uiLoop ].bLeadership ; + gMercProfiles[ uiLoop ].bMedical = tempProfiles[ uiLoop ].bMedical ; + gMercProfiles[ uiLoop ].bMechanical = tempProfiles[ uiLoop ].bMechanical ; + gMercProfiles[ uiLoop ].bExpLevel = tempProfiles[ uiLoop ].bExpLevel ; + + gMercProfiles[ uiLoop ].bEvolution = tempProfiles[ uiLoop ].bEvolution ; + gMercProfiles[ uiLoop ].bSkillTrait = tempProfiles[ uiLoop ].bSkillTrait ; + gMercProfiles[ uiLoop ].bSkillTrait2 = tempProfiles[ uiLoop ].bSkillTrait2 ; + + memcpy( &(gMercProfiles[ uiLoop ].bBuddy), &(tempProfiles[ uiLoop ].bBuddy), 5 * sizeof (INT8)); + gMercProfiles[ uiLoop ].bLearnToLike = tempProfiles[ uiLoop ].bLearnToLike ; + gMercProfiles[ uiLoop ].bLearnToLikeTime = tempProfiles[ uiLoop ].bLearnToLikeTime ; + + memcpy( &(gMercProfiles[ uiLoop ].bHated), &(tempProfiles[ uiLoop ].bHated), 5 * sizeof (INT8)); + memcpy( &(gMercProfiles[ uiLoop ].bHatedTime), &(tempProfiles[ uiLoop ].bHatedTime), 5 * sizeof (INT8)); + gMercProfiles[ uiLoop ].bLearnToHate = tempProfiles[ uiLoop ].bLearnToHate ; + gMercProfiles[ uiLoop ].bLearnToHateTime = tempProfiles[ uiLoop ].bLearnToHateTime ; + + gMercProfiles[ uiLoop ].sSalary = tempProfiles[ uiLoop ].sSalary ; + gMercProfiles[ uiLoop ].uiWeeklySalary = tempProfiles[ uiLoop ].uiWeeklySalary ; + gMercProfiles[ uiLoop ].uiBiWeeklySalary = tempProfiles[ uiLoop ].uiBiWeeklySalary ; + gMercProfiles[ uiLoop ].bMedicalDeposit = tempProfiles[ uiLoop ].bMedicalDeposit ; + gMercProfiles[ uiLoop ].sMedicalDepositAmount = tempProfiles[ uiLoop ].sMedicalDepositAmount ; + gMercProfiles[ uiLoop ].usOptionalGearCost = tempProfiles[ uiLoop ].usOptionalGearCost ; + + gMercProfiles[ uiLoop ].bArmourAttractiveness = tempProfiles[ uiLoop ].bArmourAttractiveness ; + gMercProfiles[ uiLoop ].bMainGunAttractiveness = tempProfiles[ uiLoop ].bMainGunAttractiveness ; + + memcpy( &(gMercProfiles[ uiLoop ].usApproachFactor), &(tempProfiles[ uiLoop ].usApproachFactor), 4 * sizeof (UINT16)); + if (tempProfiles[ uiLoop ].fGoodGuy) + { + gMercProfiles[ uiLoop ].ubMiscFlags3 |= PROFILE_MISC_FLAG3_GOODGUY; + } +} + +void OverwriteMercOpinionsWithXMLData( UINT32 uiLoop ) +{ + UINT8 cnt; + + for (cnt=0; cnt<75; cnt++ ) + { + gMercProfiles[ uiLoop ].bMercOpinion[cnt] = tempProfiles[ uiLoop ].bMercOpinion[cnt] ; + } +} \ No newline at end of file diff --git a/Tactical/Soldier Profile.h b/Tactical/Soldier Profile.h index d4b85aff..11102fee 100644 --- a/Tactical/Soldier Profile.h +++ b/Tactical/Soldier Profile.h @@ -197,4 +197,90 @@ SOLDIERTYPE * SwapLarrysProfiles( SOLDIERTYPE * pSoldier ); BOOLEAN DoesNPCOwnBuilding( SOLDIERTYPE *pSoldier, INT16 sGridNo ); +// HEADROCK PROFEX: Temporary array for merc profile data, read from XML +typedef struct +{ + CHAR16 zName[NAME_LENGTH]; + CHAR16 zNickname[ NICKNAME_LENGTH ]; + UINT8 ubFaceIndex; + UINT16 usEyesX; + UINT16 usEyesY; + UINT16 usMouthX; + UINT16 usMouthY; + UINT32 uiEyeDelay; + UINT32 uiMouthDelay; + UINT32 uiBlinkFrequency; + UINT32 uiExpressionFrequency; + + PaletteRepID PANTS; + PaletteRepID VEST; + PaletteRepID SKIN; + PaletteRepID HAIR; + + INT8 bSex; + UINT8 ubBodyType; + UINT32 uiBodyTypeSubFlags; + + INT8 bAttitude; + INT8 bPersonalityTrait; + UINT8 ubNeedForSleep; + + INT8 bReputationTolerance; + INT8 bDeathRate; + + INT8 bLifeMax; + INT8 bLife; + INT8 bStrength; + INT8 bAgility; + INT8 bDexterity; + INT8 bWisdom; + INT8 bMarksmanship; + INT8 bExplosive; + INT8 bLeadership; + INT8 bMedical; + INT8 bMechanical; + + INT8 bExpLevel; + + INT8 bEvolution; + INT8 bSkillTrait; + INT8 bSkillTrait2; + + INT8 bBuddy[5]; + INT8 bLearnToLike; + INT8 bLearnToLikeTime; + + INT8 bHated[5]; + INT8 bHatedTime[5]; + INT8 bLearnToHate; + INT8 bLearnToHateTime; + + INT16 sSalary; + UINT32 uiWeeklySalary; + UINT32 uiBiWeeklySalary; + INT8 bMedicalDeposit; + UINT16 sMedicalDepositAmount; + UINT16 usOptionalGearCost; + + INT8 bArmourAttractiveness; + INT8 bMainGunAttractiveness; + + // This boolean DOES NOT EXIST in gMercProfiles - it is part of a flag set called ubMiscFlags. This code + // reads the boolean and applies the flag to that flagset, if true. + BOOLEAN fGoodGuy; + + UINT16 usApproachFactor[4]; + + INT8 bMercOpinion[75]; + +} TEMPPROFILETYPE; + +extern TEMPPROFILETYPE tempProfiles[NUM_PROFILES+1]; + +extern BOOLEAN WriteMercProfiles(); +extern BOOLEAN WriteMercOpinions(); + +void OverwriteMercProfileWithXMLData( UINT32 uiLoop ); +void OverwriteMercOpinionsWithXMLData( UINT32 uiLoop ); + #endif diff --git a/Tactical/Tactical Save.cpp b/Tactical/Tactical Save.cpp index d18fcd1b..d02024a0 100644 --- a/Tactical/Tactical Save.cpp +++ b/Tactical/Tactical Save.cpp @@ -2646,6 +2646,30 @@ BOOLEAN SetSectorFlag( INT16 sMapX, INT16 sMapY, UINT8 bMapZ, UINT32 uiFlagToSet { if( uiFlagToSet == SF_ALREADY_VISITED ) { + + // HEADROCK HAM 3.5: This is no longer required at all. + //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // HEADROCK HAM 3.4: Externalized. Hidden facilities listed on the facility array will be + // added to the sector info now, upon the first visit. + + /*for(UINT32 cnt=0; (gFacilityLocations[cnt].uiSectorID >= 0 && gFacilityLocations[cnt].uiSectorID <= 256); cnt++ ) + { + // Does the current record match the current sector? + if (gFacilityLocations[cnt].uiSectorID == SECTOR( sMapX, sMapY )) + { + // Is the current record set to be revealed? + if (gFacilityLocations[cnt].fHidden == 1) + { + // Reveal the facility. + SectorInfo[ SECTOR( sMapX, sMapY) ].uiFacilitiesFlags |= (1 << (gFacilityLocations[cnt].uiFacilityType - 1)); + } + } + }*/ + + + // HEADROCK HAM 3.4: Externalized. + /* // do certain things when particular sectors are visited if ( ( sMapX == TIXA_SECTOR_X ) && ( sMapY == TIXA_SECTOR_Y ) ) { @@ -2660,7 +2684,7 @@ BOOLEAN SetSectorFlag( INT16 sMapX, INT16 sMapY, UINT8 bMapZ, UINT32 uiFlagToSet SectorInfo[ SEC_H14 ].uiFacilitiesFlags |= SFCF_GUN_RANGE; SectorInfo[ SEC_I13 ].uiFacilitiesFlags |= SFCF_GUN_RANGE; SectorInfo[ SEC_I14 ].uiFacilitiesFlags |= SFCF_GUN_RANGE; - } + }*/ if ( !GetSectorFlagStatus( sMapX, sMapY, bMapZ, SF_ALREADY_VISITED ) ) { diff --git a/Tactical/Tactical_2005Express.vcproj b/Tactical/Tactical_2005Express.vcproj index 76f95819..929ee038 100644 --- a/Tactical/Tactical_2005Express.vcproj +++ b/Tactical/Tactical_2005Express.vcproj @@ -1,7 +1,7 @@ + + + + + + diff --git a/Tactical/Tactical_VS2008.vcproj b/Tactical/Tactical_VS2008.vcproj index bca412b4..af68c146 100644 --- a/Tactical/Tactical_VS2008.vcproj +++ b/Tactical/Tactical_VS2008.vcproj @@ -603,6 +603,10 @@ RelativePath="XML.h" > + + + + + + diff --git a/Tactical/TeamTurns.cpp b/Tactical/TeamTurns.cpp index a00da440..b1aa3a50 100644 --- a/Tactical/TeamTurns.cpp +++ b/Tactical/TeamTurns.cpp @@ -48,6 +48,8 @@ #include "NPC.h" #endif +// HEADROCK HAM 3.2: Gamesettings.h for external modifications to team turns. +#include "GameSettings.h" #include "Reinforcement.h" #include "fresh_header.h" //forward declarations of common classes to eliminate includes @@ -315,9 +317,15 @@ void EndTurn( UINT8 ubNextTeam ) } else { - AddPossiblePendingEnemiesToBattle(); - - AddPossiblePendingMilitiaToBattle(); + // HEADROCK HAM 3.2: Experimental fix to force reinforcements enter battle with 0 APs. + if (gGameExternalOptions.ubReinforcementsFirstTurnFreeze != 1 && gGameExternalOptions.ubReinforcementsFirstTurnFreeze != 2) + { + AddPossiblePendingEnemiesToBattle(); + } + if (gGameExternalOptions.ubReinforcementsFirstTurnFreeze != 1 && gGameExternalOptions.ubReinforcementsFirstTurnFreeze != 3) + { + AddPossiblePendingMilitiaToBattle(); + } // InitEnemyUIBar( ); @@ -337,6 +345,16 @@ void EndTurn( UINT8 ubNextTeam ) if(is_server || !is_client) BeginTeamTurn( gTacticalStatus.ubCurrentTeam ); + // HEADROCK HAM 3.2: Experimental fix to force reinforcements enter battle with 0 APs. + if (gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 1 || gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 2) + { + AddPossiblePendingEnemiesToBattle(); + } + if (gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 1 || gGameExternalOptions.ubReinforcementsFirstTurnFreeze == 3) + { + AddPossiblePendingMilitiaToBattle(); + } + BetweenTurnsVisibilityAdjustments(); } } diff --git a/Tactical/Turn Based Input.cpp b/Tactical/Turn Based Input.cpp index 0aa5b286..cbf8d153 100644 --- a/Tactical/Turn Based Input.cpp +++ b/Tactical/Turn Based Input.cpp @@ -1259,9 +1259,11 @@ void GetTBMousePositionInput( UINT32 *puiNewEvent ) } usOldMapPos = sMapPos; + } } + void GetPolledKeyboardInput( UINT32 *puiNewEvent ) { static BOOLEAN fShifted = FALSE; @@ -1406,6 +1408,7 @@ void GetPolledKeyboardInput( UINT32 *puiNewEvent ) fEndDown = FALSE; } + } @@ -3390,9 +3393,20 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) } 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 ) ) + // HEADROCK HAM 3.5: When this INI setting is enabled, ALL mercs in the current sector will do a goggle swap. + if (gGameExternalOptions.fGoggleSwapAffectsAllMercsInSector) { - SwapGogglesUniformly(pTeamSoldier, fToNightVision); + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->sSectorX == gWorldSectorX && pTeamSoldier->sSectorY == gWorldSectorY && pTeamSoldier->bSectorZ == gbWorldSectorZ && !AM_A_ROBOT( pTeamSoldier ) ) + { + SwapGogglesUniformly(pTeamSoldier, fToNightVision); + } + } + else + { + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->bAssignment == CurrentSquad( ) && !AM_A_ROBOT( pTeamSoldier ) ) + { + SwapGogglesUniformly(pTeamSoldier, fToNightVision); + } } } } @@ -3400,9 +3414,20 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) { 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 ) ) + // HEADROCK HAM 3.5: When this INI setting is enabled, ALL mercs in the current sector will do a goggle swap. + if (gGameExternalOptions.fGoggleSwapAffectsAllMercsInSector) { - SwapGoggles(pTeamSoldier); + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->sSectorX == gWorldSectorX && pTeamSoldier->sSectorY == gWorldSectorY && pTeamSoldier->bSectorZ == gbWorldSectorZ && !AM_A_ROBOT( pTeamSoldier ) ) + { + SwapGoggles(pTeamSoldier); + } + } + else + { + if ( OK_CONTROLLABLE_MERC( pTeamSoldier ) && pTeamSoldier->bAssignment == CurrentSquad( ) && !AM_A_ROBOT( pTeamSoldier ) ) + { + SwapGoggles(pTeamSoldier); + } } } } @@ -3772,11 +3797,11 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) //if the display cover or line of sight is being displayed if( _KeyDown( END ) || _KeyDown( DEL ) ) { - //f( _KeyDown( DEL ) ) - //ChangeSizeOfDisplayCover( gGameSettings.ubSizeOfDisplayCover + 1 ); + //if( _KeyDown( DEL ) ) + // ChangeSizeOfDisplayCover( gGameSettings.ubSizeOfDisplayCover + 1 ); //if( _KeyDown( END ) ) - //ChangeSizeOfLOS( gGameSettings.ubSizeOfLOS + 1 ); + // ChangeSizeOfLOS( gGameSettings.ubSizeOfLOS + 1 ); } else { @@ -4019,10 +4044,10 @@ void GetKeyboardInput( UINT32 *puiNewEvent ) if( _KeyDown( END ) || _KeyDown( DEL ) ) { //if( _KeyDown( DEL ) ) - //ChangeSizeOfDisplayCover( gGameSettings.ubSizeOfDisplayCover - 1 ); + // ChangeSizeOfDisplayCover( gGameSettings.ubSizeOfDisplayCover - 1 ); //if( _KeyDown( END ) ) - //ChangeSizeOfLOS( gGameSettings.ubSizeOfLOS - 1 ); + // ChangeSizeOfLOS( gGameSettings.ubSizeOfLOS - 1 ); } else { @@ -5712,149 +5737,274 @@ bool BadGoggles(SOLDIERTYPE *pTeamSoldier) { void SwapGoggles(SOLDIERTYPE *pTeamSoldier) { - /* 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++) + // WDS - Smart goggle switching + // NOTE: Investigate using GetItemVisionRangeBonus from Items.cpp??? + if (gGameExternalOptions.smartGoggleSwitch) { - 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 ) + // 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) { - itemFound = true; - bestBonus = 0; - pGoggles = FindNightGogglesInInv( pTeamSoldier ); - //search for better goggles on the helmet - if (pGoggles) + if ( (Item[pTeamSoldier->inv[headSlot].usItem].brightlightvisionrangebonus > 0) ) { - bestBonus = Item[pGoggles->usItem].nightvisionrangebonus; - } - //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; - } - } - } - if ( pGoggles ) - { - SwapObjs( pTeamSoldier, bSlot1, pGoggles, TRUE ); + slotToUse = headSlot; 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 + } + else if ( (Item[pTeamSoldier->inv[headSlot].usItem].nightvisionrangebonus > 0) ) { - // Remove sungoggles. - PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + slotToUse = headSlot; break; + } + else if (pTeamSoldier->inv[headSlot].exists() == false) + { + slotToUse = headSlot; } } - // else if wearing NVGs - else if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0) + + if (slotToUse == -1) { - itemFound = true; - bestBonus = 0; - pGoggles = FindSunGogglesInInv( pTeamSoldier ); - //search for better goggles on the helmet - if (pGoggles) + // 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()) { - bestBonus = Item[pGoggles->usItem].brightlightvisionrangebonus; + SwapObjs( pTeamSoldier, slotToUse, pGoggles, TRUE ); + } + else + { + pGoggles->MoveThisObjectTo(pTeamSoldier->inv[slotToUse], 1, pTeamSoldier, slotToUse); } - //search helmet and vest - for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) + } + else + { + // No goggles to equip, should the current ones be unequiped? + if (pTeamSoldier->inv[slotToUse].exists()) { - pObj = &(pTeamSoldier->inv[gear]); - for (attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) + if ((DayTime() && (Item[pTeamSoldier->inv[slotToUse].usItem].nightvisionrangebonus > 0)) || + (!DayTime() && (Item[pTeamSoldier->inv[slotToUse].usItem].brightlightvisionrangebonus > 0))) { - if ( Item[ iter->usItem ].brightlightvisionrangebonus > bestBonus && Item[ iter->usItem ].usItemClass == IC_FACE ) + // It's day and we're wearing night goggles (or vice-versa), find a place to stash them + if (pTeamSoldier->inv[ HELMETPOS ].exists()) { - pGoggles = &(*iter); - bestBonus = Item[ iter->usItem ].brightlightvisionrangebonus; + 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 + if (ValidAttachment( pTeamSoldier->inv[slotToUse].usItem, pTeamSoldier->inv[HELMETPOS].usItem ) && + pTeamSoldier->inv[slotToUse][0]->attachments.size() < MAX_ATTACHMENTS) + { + pTeamSoldier->inv[HELMETPOS].AttachObject( pTeamSoldier, &pTeamSoldier->inv[slotToUse], FALSE, 0 ); + } + else + { + // Remove sungoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[slotToUse], FALSE); + } + } + } + else + { + // Try dumping it anywhere in inventory given there's no helemt + if (ValidAttachment( pTeamSoldier->inv[slotToUse].usItem, pTeamSoldier->inv[HELMETPOS].usItem ) && + pTeamSoldier->inv[slotToUse][0]->attachments.size() < MAX_ATTACHMENTS) + { + pTeamSoldier->inv[HELMETPOS].AttachObject( pTeamSoldier, &pTeamSoldier->inv[slotToUse], FALSE, 0 ); + } + else + { + // Remove sungoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[slotToUse], FALSE); + } } } } - 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) + } + else + { + // Normal goggle switching + /* 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++) { - bestBonus = 0; - // search helmet and vest for goggles of some kind - for(UINT8 gear = HELMETPOS; gear <= VESTPOS; gear++) + 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 ) { - pObj = &(pTeamSoldier->inv[gear]); - for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); ++iter) + itemFound = true; + bestBonus = 0; + pGoggles = FindNightGogglesInInv( pTeamSoldier ); + //search for better goggles on the helmet + if (pGoggles) { - if(DayTime() == TRUE && Item[iter->usItem].brightlightvisionrangebonus > bestBonus && Item[iter->usItem].usItemClass == IC_FACE) + bestBonus = Item[pGoggles->usItem].nightvisionrangebonus; + } + //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) { - 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 ( 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) + 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 + { + if (ValidAttachment( pTeamSoldier->inv[bSlot1].usItem, pTeamSoldier->inv[HELMETPOS].usItem ) && + pTeamSoldier->inv[bSlot1][0]->attachments.size() < MAX_ATTACHMENTS) + { + pTeamSoldier->inv[HELMETPOS].AttachObject( pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE, 0 ); + break; + } + else + { + // Remove sungoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + break; + } + } } - else + // else if wearing NVGs + else if(Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus > 0) { - break; + 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 + { + if (ValidAttachment( pTeamSoldier->inv[bSlot1].usItem, pTeamSoldier->inv[HELMETPOS].usItem ) && + pTeamSoldier->inv[bSlot1][0]->attachments.size() < MAX_ATTACHMENTS) + { + pTeamSoldier->inv[HELMETPOS].AttachObject( pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE, 0 ); + break; + } + 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; @@ -5922,9 +6072,18 @@ void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision) } else if (Item[pTeamSoldier->inv[bSlot1].usItem].nightvisionrangebonus <= 0) { - // Remove sungoggles. - PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); - break; + if (ValidAttachment( pTeamSoldier->inv[bSlot1].usItem, pTeamSoldier->inv[HELMETPOS].usItem ) && + pTeamSoldier->inv[bSlot1][0]->attachments.size() < MAX_ATTACHMENTS) + { + pTeamSoldier->inv[HELMETPOS].AttachObject( pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE, 0 ); + break; + } + else + { + // Remove nightgoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + break; + } } } } @@ -5961,9 +6120,18 @@ void SwapGogglesUniformly(SOLDIERTYPE *pTeamSoldier, BOOLEAN fToNightVision) } else if (Item[pTeamSoldier->inv[bSlot1].usItem].brightlightvisionrangebonus <= 0) { - // Remove nightgoggles. - PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); - break; + if (ValidAttachment( pTeamSoldier->inv[bSlot1].usItem, pTeamSoldier->inv[HELMETPOS].usItem ) && + pTeamSoldier->inv[bSlot1][0]->attachments.size() < MAX_ATTACHMENTS) + { + pTeamSoldier->inv[HELMETPOS].AttachObject( pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE, 0 ); + break; + } + else + { + // Remove nightgoggles. + PlaceInAnyPocket(pTeamSoldier, &pTeamSoldier->inv[bSlot1], FALSE); + break; + } } } } @@ -6038,6 +6206,8 @@ void SeperateItems() CreateAmmo(gWorldItems[ uiLoop ].object[x]->data.gun.usGunAmmoItem, &gTempObject, gWorldItems[ uiLoop ].object[x]->data.gun.ubGunShotsLeft); gWorldItems[ uiLoop ].object[x]->data.gun.ubGunShotsLeft = 0; gWorldItems[ uiLoop ].object[x]->data.gun.usGunAmmoItem = NONE; + // HEADROCK HAM 3.5: Clear ammo type + gWorldItems[ uiLoop ].object[x]->data.gun.ubGunAmmoType = NONE; // put it on the ground AddItemToPool( gWorldItems[ uiLoop ].sGridNo, &gTempObject, 1, gWorldItems[ uiLoop ].ubLevel, WORLD_ITEM_REACHABLE , -1 ); diff --git a/Tactical/UI Cursors.cpp b/Tactical/UI Cursors.cpp index d92c1da5..071b3aab 100644 --- a/Tactical/UI Cursors.cpp +++ b/Tactical/UI Cursors.cpp @@ -387,8 +387,10 @@ UINT8 HandleActivatedTargetCursor( SOLDIERTYPE *pSoldier, INT16 sMapPos, BOOLEAN pSoldier->bDoAutofire++; sAPCosts = CalcTotalAPsToAttack( pSoldier, sMapPos, TRUE, 0); } + while(EnoughPoints( pSoldier, sAPCosts, 0, FALSE ) && sAPCosts == sCurAPCosts && pSoldier->inv[ pSoldier->ubAttackingHand ][0]->data.gun.ubGunShotsLeft >= pSoldier->bDoAutofire); pSoldier->bDoAutofire--; + } gfUIAutofireBulletCount = TRUE; @@ -579,7 +581,7 @@ UINT8 HandleActivatedTargetCursor( SOLDIERTYPE *pSoldier, INT16 sMapPos, BOOLEAN // and also a Targetted Bodypart indicator. if(pSoldier->bDoAutofire == 0 && gGameSettings.fOptions[ TOPTION_CTH_CURSOR ]) { - if (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 2) + if (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 2) { // Burst mode only OBJECTTYPE * pInHand; @@ -648,7 +650,7 @@ UINT8 HandleActivatedTargetCursor( SOLDIERTYPE *pSoldier, INT16 sMapPos, BOOLEAN else if(pSoldier->bDoAutofire > 0 && gGameSettings.fOptions[ TOPTION_CTH_CURSOR ]) { - if (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) + if (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) { gbCtHBurstCount = 1; @@ -1412,9 +1414,9 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f 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) ) + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 2) ) gfUIBodyHitLocation = TRUE; - else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) ) gfUIBodyHitLocation = TRUE; return; } @@ -1441,9 +1443,9 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f 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) ) + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 2) ) gfUIBodyHitLocation = TRUE; - else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) ) gfUIBodyHitLocation = TRUE; break; @@ -1453,9 +1455,9 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f 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) ) + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 2) ) gfUIBodyHitLocation = TRUE; - else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) ) gfUIBodyHitLocation = TRUE; break; @@ -1465,9 +1467,9 @@ void DetermineCursorBodyLocation( UINT8 ubSoldierID, BOOLEAN fDisplay, BOOLEAN f 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) ) + else if ( pSoldier->bDoBurst && !pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 2) ) gfUIBodyHitLocation = TRUE; - else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + else if ( pSoldier->bDoBurst && pSoldier->bDoAutofire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) ) gfUIBodyHitLocation = TRUE; break; } diff --git a/Tactical/Vehicles.cpp b/Tactical/Vehicles.cpp index 74e5e5f2..92e8d8f1 100644 --- a/Tactical/Vehicles.cpp +++ b/Tactical/Vehicles.cpp @@ -310,7 +310,18 @@ INT32 AddVehicleToList( INT16 sMapX, INT16 sMapY, INT16 sGridNo, UINT8 ubType ) Assert( 0 ); } - pGroup->ubTransportationMask = (UINT8)iMvtTypes[ ubType ]; + + // HEADROCK HAM 3.1: An INI setting allows us to turn the Hummer into a true offroad vehicle. It will use the + // "TRUCK" type movement, which allows it to go into mild non-road terrain. I wish I could come with a more + // subtle method than this crude override, but this is what I've got at the moment. + if (gGameExternalOptions.fHumveeOffroad && ubType == HUMMER) + { + pGroup->ubTransportationMask = TRUCK; + } + else + { + pGroup->ubTransportationMask = (UINT8)iMvtTypes[ ubType ]; + } // ARM: setup group movement defaults pGroup->ubSectorX = ( UINT8 ) sMapX; diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 0c9ef229..14ff6dd7 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -1740,9 +1740,9 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT16 sTargetGridNo ) // uiHitChance = MINCHANCETOHIT; //else // uiHitChance -= 30; - if(uiHitChance <= (UINT16)(__max(30, gGameExternalOptions.iMinimumCTH + 30))) + if(uiHitChance <= (UINT16)(__max(30, gGameExternalOptions.ubMinimumCTH + 30))) { - uiHitChance = gGameExternalOptions.iMinimumCTH ; + uiHitChance = gGameExternalOptions.ubMinimumCTH ; } else { @@ -3564,13 +3564,16 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, bool highPowerScope = false; UINT32 pScope; + // HEADROCK HAM 3.5: Variable holds total autofire penalty. + INT16 sTotalAutofirePenalty = 0; + DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("CalcChanceToHitGun")); if ( pSoldier->stats.bMarksmanship == 0 ) { // HEADROCK: (HAM) Altered to accept external arguments // return( MINCHANCETOHIT ); - return( gGameExternalOptions.iMinimumCTH ); + return( gGameExternalOptions.ubMinimumCTH ); } // make sure the guy's actually got a weapon in his hand! @@ -3766,54 +3769,8 @@ 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; - } - } + // HEADROCK HAM 3.5: Store the penalty + sTotalAutofirePenalty = iPenalty; } //ADB we need to calculate the distance visible and SoldierTo...LOSTests that we want to @@ -4046,7 +4003,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, // 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 ) + if ( gGameExternalOptions.ubAimPenaltyPerTargetShock > 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. @@ -4058,7 +4015,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, UINT16 MIN_RANGE_FOR_FULL_COWER; UINT16 MAX_TARGET_COWERING_PENALTY; - AIM_PENALTY_PER_TARGET_SHOCK = gGameExternalOptions.iAimPenaltyPerTargetShock; + AIM_PENALTY_PER_TARGET_SHOCK = gGameExternalOptions.ubAimPenaltyPerTargetShock; MIN_RANGE_FOR_FULL_COWER = gGameExternalOptions.usMinRangeForFullCoweringPenalty; MAX_TARGET_COWERING_PENALTY = gGameExternalOptions.usMaxTargetCoweringPenalty; @@ -4201,6 +4158,88 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, */ } + // HEADROCK HAM 3.5: Moved this here for now. + // 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.ubRealisticTracers > 0 ) + { + UINT16 iBulletsLeft, iTracersFired, iBulletsPerTracer, iBulletsSinceLastTracer; + UINT8 cnt; + //UINT16 iAutoPenaltySinceLastTracer; + iTracersFired = 0; + iBulletsPerTracer = gGameExternalOptions.ubNumBulletsPerTracer; + + // 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 ) + { + // HEADROCK HAM 3.5: I'm going to revise this - my current system makes no sense at all. What was I + // thinking?! + + // Correct all autofire penalty so far + //iBonus = iPenalty; + + // Add Tracer Bump if previous bullet was a tracer + //iBonus += (gGameExternalOptions.ubCTHBumpPerTracer * iTracersFired); + iBonus = (gGameExternalOptions.ubCTHBumpPerTracer * iTracersFired); + + // Calculate penalty since last tracer was fired + UINT8 ubAutoPenaltySinceLastTracer = GetAutoPenalty(pInHand, gAnimControl[ pSoldier->usAnimState ].ubEndHeight == ANIM_PRONE) * iBulletsSinceLastTracer; + if ( HAS_SKILL_TRAIT( pSoldier, AUTO_WEAPS ) ) + { + ubAutoPenaltySinceLastTracer /= 2 * NUM_SKILL_TRAITS( pSoldier, AUTO_WEAPS ); + } + // Add penalty to bonus. + //iBonus -= iAutoPenaltySinceLastTracer; + + /////////////////////////////////////////////////// + // HEADROCK HAM 3.5: Limit maximum bonus by range. + + INT16 sBaseChance = iChance + sTotalAutofirePenalty; + + // We don't want to enforce a limit unless the tracers have actually put us over the original CtH. + if (sBaseChance <= iChance+iBonus) // Base_Chance without AutoPen, less or equal to Current_Chance plus tracer bumps + { + // store lowest: Chance+Tracer bumps, or Range-enforced limit + INT16 sChanceLimit = __min(iChance+iBonus, sBaseChance+(((iRange-100) / CELL_X_SIZE) * gGameExternalOptions.ubRangeDifficultyAimingWithTracers)); + // store highest: Chance Delta or base CtH + //sChanceDelta = __max(sChanceDelta, sBaseChance); + + // iBonus is the distance between the enforced limit (if any) and the current chance with all penalties so far. + // But it can't be negative, 'cause it's a bonus. + iBonus = __max(0,sChanceLimit - iChance); + // Add autopenalty since last tracer + if (iBulletsSinceLastTracer < iBulletsPerTracer) + { + iBonus -= ubAutoPenaltySinceLastTracer; + } + + } + iChance += iBonus; + } + } + // adjust for roof/not on roof if ( pSoldier->pathing.bLevel == 0 ) { @@ -4407,7 +4446,7 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, // 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 (iChance <= gGameExternalOptions.ubMinimumCTH) { if ( TANK( pSoldier ) ) { @@ -4416,10 +4455,10 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, } else { - iChance = gGameExternalOptions.iMinimumCTH; - if ( gGameExternalOptions.iMinimumCTH == 0 ) + iChance = gGameExternalOptions.ubMinimumCTH; + if ( gGameExternalOptions.ubMinimumCTH == 0 ) { - if ( PreRandom( gGameExternalOptions.iMinimumCTHDivisor ) == (gGameExternalOptions.iMinimumCTHDivisor - 1) ) + if ( PreRandom( gGameExternalOptions.usMinimumCTHDivisor ) == (gGameExternalOptions.usMinimumCTHDivisor - 1) ) { iChance = 1; } @@ -4431,8 +4470,8 @@ UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT16 sGridNo, INT16 ubAimTime, // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI // if (iChance > MAXCHANCETOHIT) // iChance = MAXCHANCETOHIT; - if (iChance > gGameExternalOptions.iMaximumCTH) - iChance = gGameExternalOptions.iMaximumCTH; + if (iChance > gGameExternalOptions.ubMaximumCTH) + iChance = gGameExternalOptions.ubMaximumCTH; } // NumMessage("ChanceToHit = ",chance); @@ -4778,7 +4817,9 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat { case AIM_SHOT_HEAD: // is the blow deadly enough for an instant kill? - if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT )) + // HEADROCK HAM 3.6: Reattached "Max Distance For Messy Death" tag from the XML! God knows why it wasn't attached when they MADE THAT TAG. + //if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT )) + if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= Weapon[ pFirer->usAttackingWeapon ].maxdistformessydeath ) { if (iImpactForCrits > MIN_DAMAGE_FOR_INSTANT_KILL && iImpactForCrits < pTarget->stats.bLife) { @@ -4825,7 +4866,9 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat // normal damage to torso // is the blow deadly enough for an instant kill? // since this value is much lower than the others, it only applies at short range... - if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT )) + // HEADROCK HAM 3.6: Reattached "Max Distance For Messy Death" tag from the XML! God knows why it wasn't attached when they MADE THAT TAG. + //if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT )) + if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= Weapon[ pFirer->usAttackingWeapon ].maxdistformessydeath ) { if (iImpact > MIN_DAMAGE_FOR_INSTANT_KILL && iImpact < pTarget->stats.bLife) { @@ -4919,6 +4962,17 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat { ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, Message[STR_HEAD_HIT], pTarget->name ); } + + // HEADROCK HAM 3.2: Critical headshots may now cause blindness, based on shot damage. + if (gGameExternalOptions.ubChanceBlindedByHeadshot) + { + if (PreRandom(gGameExternalOptions.ubChanceBlindedByHeadshot) == 0) + { + if (pTarget->bBlindedCounter < iImpact / 10 ) + pTarget->bBlindedCounter = iImpact / 10; + } + } + break; case AIM_SHOT_TORSO: if (PreRandom( 1 ) == 0 && !(pTarget->flags.uiStatusFlags & SOLDIER_MONSTER) ) @@ -5489,17 +5543,17 @@ UINT32 CalcChanceHTH( SOLDIERTYPE * pAttacker,SOLDIERTYPE *pDefender, INT16 ubAi // MAKE SURE CHANCE TO HIT IS WITHIN DEFINED LIMITS // HEADROCK: I urinate on your Defined Limits! Power Rangers, Externalize! // Disclaimer: No offense meant, all in good fun ;) - if (iChance < gGameExternalOptions.iMinimumCTH) + if (iChance < gGameExternalOptions.ubMinimumCTH) { - iChance = gGameExternalOptions.iMinimumCTH; + iChance = gGameExternalOptions.ubMinimumCTH; } else { // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI //if (iChance > MAXCHANCETOHIT) // iChance = MAXCHANCETOHIT; - if (iChance > gGameExternalOptions.iMaximumCTH) - iChance = gGameExternalOptions.iMaximumCTH; + if (iChance > gGameExternalOptions.ubMaximumCTH) + iChance = gGameExternalOptions.ubMaximumCTH; } //NumMessage("ChanceToStab = ",chance); @@ -5955,17 +6009,23 @@ 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; + // HEADROCK HAM 3.2: External divisor for CTH with mortars, now that they are more prevalent in the battlefield. + if ( Item[ usHandItem ].mortar ) + { + iChance = iChance / gGameExternalOptions.ubMortarCTHDivisor; + } + // 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; + if (iChance < gGameExternalOptions.ubMinimumCTH) + iChance = gGameExternalOptions.ubMinimumCTH; else { // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI //if (iChance > MAXCHANCETOHIT) // iChance = MAXCHANCETOHIT; - if (iChance > gGameExternalOptions.iMaximumCTH) - iChance = gGameExternalOptions.iMaximumCTH; + if (iChance > gGameExternalOptions.ubMaximumCTH) + iChance = gGameExternalOptions.ubMaximumCTH; } @@ -6163,7 +6223,7 @@ UINT8 GetAutofireShotsPerFiveAPs( OBJECTTYPE *pObj ) // HEADROCK HAM B2.6: Added overall modifier if (Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP > 0) { - return __max((Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP + gGameExternalOptions.iAutofireBulletsPer5APModifier), 0); + return __max((Weapon[ pObj->usItem ].bAutofireShotsPerFiveAP + gGameExternalOptions.bAutofireBulletsPer5APModifier), 0); } else return 0; @@ -6176,6 +6236,13 @@ UINT16 GetMagSize( OBJECTTYPE *pObj ) return Weapon[ pObj->usItem ].ubMagSize + GetMagSizeBonus(pObj); } + +// HEADROCK HAM 3.3: Function to get a weapon's current ammotype. +UINT8 GetAmmoType( OBJECTTYPE *pObj ) +{ + return (*pObj)[0]->data.gun.ubGunAmmoType; +} + bool WeaponReady(SOLDIERTYPE * pSoldier) { #ifdef ROBOT_ALWAYS_READY @@ -6198,9 +6265,139 @@ INT8 GetAPsToReload( OBJECTTYPE *pObj ) } +// HEADROCK HAM 3.4: Estimates the number of bullets left in the gun. For use during combat. +CHAR16 gBulletCount[10]; // This is a global containing the bullet count string +void EstimateBulletsLeft( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj ) +{ + UINT16 usExpLevel; + UINT16 usDexterity; + UINT16 usWisdom; + UINT8 ubMagSize = Weapon[pObj->usItem].ubMagSize; + UINT16 usRealBulletCount = (*pObj)[0]->data.gun.ubGunShotsLeft; + UINT16 i = 0; + BOOLEAN fPsycho = FALSE; + INT16 sEffectiveSkill; + INT8 bDeviation = 0; + // HEADROCK HAM 3.5: Bugfix, failsafe + if ( pSoldier == NULL ) + { + // No soldier... Return true count. + swprintf(gBulletCount, L"%d", usRealBulletCount); + return; + } + usExpLevel = EffectiveExpLevel(pSoldier); + usDexterity = EffectiveDexterity(pSoldier); + usWisdom = EffectiveWisdom(pSoldier); + if ( gGameExternalOptions.usBulletHideIntensity <= 0 ) + { + // Feature is disabled. Print the real bullet count. + swprintf(gBulletCount, L"%d", usRealBulletCount); + return; + } + // Is this Soldier a psycho? + if ( pSoldier->ubProfile != NO_PROFILE && gMercProfiles[ pSoldier->ubProfile ].bPersonalityTrait == PSYCHO ) + { + fPsycho = TRUE; + } + + // High EXP Level, Wisdom and Dexterity required for any estimation to be possible. + // When Experience goes up, the required WIS+DEX goes down. + // At ExpLevel 1 -> WIS+DEX must be > 180. A high requirement! + // At ExpLevel 2 -> WIS+DEX must be > 160. + // ... + // At ExpLevel 5 -> WIS+DEX must be > 100. Most characters have already attained estimation ability by now. + // At ExpLevel 10 -> WIS+DEX must be > 0, which is always. + if ( (usWisdom + usDexterity) < (200 - (usExpLevel * 20)) ) + { + // Soldier is not skilled enough to know how many bullets are left in the gun. Print a "??" indicating that + // the real bullet count may be anywhere between empty and full. + swprintf(gBulletCount, L"%s", L"??"); + return; + } + else // Soldier good enough for at least a rough estimate. + { + // HEADROCK HAM 3.5: Moved this here. If the character fails the above requirement, he shouldn't even know if the + // gun is empty or full. + //------------------------- + // If the magazine is empty or full, he knows it automatically. + if (usRealBulletCount == ubMagSize) + { + // Magazine is fresh. Let the soldier know this. + swprintf(gBulletCount, L"%d", usRealBulletCount); + return; + } + if (usRealBulletCount == 0) + { + // Magazine is empty, so it will also show as empty. + swprintf(gBulletCount, L"%d", usRealBulletCount); + return; + } + //------------------------- + + // Let's see by how much we've beaten the requirement! + sEffectiveSkill = ( (usWisdom + usDexterity) - (200 - (usExpLevel * 20)) ) / 2; + } + // So from now on we've got the sEffectiveSkill value, which can go from 0 to 100 + // This value represents getting better in all three stats (WIS,DEX,EXP), because as + // they go up the result of the calculation above also goes up. Having a particularly high + // value in any of the three stats, in fact, reduces the need to have high values in others. + // + // The higher your EXP level, the less WIS+DEX you need for an accurate estimate, so even + // inept characters will eventually be able to make a fair estimate, provided the EXP + // level goes high enough. + // + // On the other hand, WIS and DEX work together. In fact, it is the average of these + // skills that determines how soon you can start estimating, and how good your estimation gets + // as you gain levels. So while one high skill can bring better estimates sooner, it requires + // both skills to be improved for a really good estimate. + // + // The range of sEffectiveSkill is 0 to 100 + //`````````````````````````````````````````` + + // Psychos are eligible for an estimate just the same as any other character. But the trait reduces + // effective skill by 10, so It'll take them longer before they can get a good estimate. + sEffectiveSkill -= (fPsycho * (10)); + + sEffectiveSkill = __max(0, sEffectiveSkill); + sEffectiveSkill = __min(100, sEffectiveSkill); + + // Now, we invert the effective skill + bDeviation = 100-sEffectiveSkill; // range is still 0-100, but lower is better + // Use this as a percentage, and figure out the deviation based on magazine size and current bullet. The nearer + // you get to the bottom of a magazine, the harder it is to accurately estimate how many bullets are in there. + bDeviation = ((ubMagSize - usRealBulletCount) * (bDeviation * bDeviation)) / 10000; + + // Add externalized difficulty modifier + bDeviation = (bDeviation * gGameExternalOptions.usBulletHideIntensity) / 100; + + // If the deviation surpasses the character's EXP level, then he/she is not able to know how many bullets are + // left in the gun, but can still give a rough estimate. There are currently three estimate stages - High, Mid, + // and Low. + if (bDeviation > usExpLevel) + { + if (usRealBulletCount >= (ubMagSize*2)/3) + { + swprintf(gBulletCount, L"%s", L"?H"); + } + else if (usRealBulletCount < (ubMagSize*2)/3 && usRealBulletCount >= ubMagSize/3) + { + swprintf(gBulletCount, L"%s", L"?M"); + } + else if (usRealBulletCount < ubMagSize/3) + { + swprintf(gBulletCount, L"%s", L"?L"); + } + return; + } + + // Default - return true count. + swprintf(gBulletCount, L"%d", usRealBulletCount); + return; + +} \ No newline at end of file diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h index f96ae784..faf075e3 100644 --- a/Tactical/Weapons.h +++ b/Tactical/Weapons.h @@ -391,9 +391,14 @@ UINT8 GetBurstPenalty( OBJECTTYPE *pObj, BOOLEAN fProneStance = FALSE ); UINT8 GetAutoPenalty( OBJECTTYPE *pObj, BOOLEAN fProneStance = FALSE ); UINT8 GetShotsPerBurst( OBJECTTYPE *pObj ); UINT16 GetMagSize( OBJECTTYPE *pObj ); +UINT8 GetAmmoType( OBJECTTYPE *pObj ); bool WeaponReady(SOLDIERTYPE * pSoldier); INT8 GetAPsToReload( OBJECTTYPE *pObj ); +// HEADROCK HAM 3.4: Estimate bullets left in gun. Returns an "errorcode" telling the calling function if the check +// was successful and to what degree. +void EstimateBulletsLeft( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj ); +extern CHAR16 gBulletCount[10]; #endif diff --git a/Tactical/XML.h b/Tactical/XML.h index a232a955..a3952314 100644 --- a/Tactical/XML.h +++ b/Tactical/XML.h @@ -125,6 +125,23 @@ typedef PARSE_STAGE; #define LAPTOPFLORISTLOCATIONFILENAME "Laptop\\FloristPositions.xml" #define LAPTOPFUNERALLOCATIONFILENAME "Laptop\\FuneralPositions.xml" +// HEADROCK HAM 3.4: Facility Locations [2009-05-19] +#define SECTORFACILITIESFILENAME "Map\\Facilities.xml" +// HEADROCK HAM 3.4: Dynamic Roaming Restrictions [2009-05-19] +#define DYNAMICROAMINGFILENAME "Map\\DynamicRestrictions.xml" +// HEADROCK HAM 3.5: Facility Types [2009-06-14] +#define FACILITYTYPESFILENAME "Map\\FacilityTypes.xml" +// HEADROCK HAM 3.6: Sector Names [2009-07-27] +#define SECTORNAMESFILENAME "Map\\SectorNames.xml" +// HEADROCK PROFEX: Merc Profiles [2009-07-27] +#define MERCPROFILESFILENAME "MercProfiles.xml" +// HEADROCK PROFEX: Merc Opinions [2009-07-27] +#define MERCOPINIONSFILENAME "MercOpinions.xml" +// HEADROCK HAM 3.6: Bloodcat Placements [2009-07-31] +#define BLOODCATPLACEMENTSFILENAME "Map\\BloodcatPlacements.xml" +// HEADROCK HAM 3.6: Uniform Colors [2009-09-29] +#define UNIFORMCOLORSFILENAME "Army\\UniformColors.xml" + extern BOOLEAN ReadInItemStats(STR fileName, BOOLEAN localizedVersion); extern BOOLEAN WriteItemStats(); @@ -260,4 +277,29 @@ extern BOOLEAN ReadInFloristLocations(STR fileName); //Gotthard: Laptop Funeral Locations extern BOOLEAN ReadInFuneralLocations(STR fileName); + +// HEADROCK HAM 3.4: Sector Facility Locations +extern BOOLEAN ReadInSectorFacilities(STR fileName); + +// HEADROCK HAM 3.4: Dynamic Roaming Restrictions +extern BOOLEAN ReadInDynamicRoamingRestrictions(STR fileName); + +// HEADROCK HAM 3.5: Facility Types and bonuses +extern BOOLEAN ReadInFacilityTypes(STR fileName); + +// HEADROCK HAM 3.6: Customized Sector Names +extern BOOLEAN ReadInSectorNames(STR fileName); + +// HEADROCK PROFEX: Merc Profiles +extern BOOLEAN ReadInMercProfiles(STR fileName); + +// HEADROCK PROFEX: Merc Opinions +extern BOOLEAN ReadInMercOpinions(STR fileName); + +// HEADROCK HAM 3.6: Customized Bloodcat Placements +extern BOOLEAN ReadInBloodcatPlacements(STR fileName); + +// HEADROCK HAM 3.6: Customized Uniform Colors +extern BOOLEAN ReadInUniforms(STR fileName); + #endif diff --git a/Tactical/XML_Opinions.cpp b/Tactical/XML_Opinions.cpp new file mode 100644 index 00000000..9e38515b --- /dev/null +++ b/Tactical/XML_Opinions.cpp @@ -0,0 +1,899 @@ +/////////////////////////////////////////////////////////////////////////////// +// HEADROCK PROFEX: PROFile EXternalization +// +// This file handles all reading from Merc Profiles.XML. It offers an external +// alternative to PROEDIT. Values that have no current use in the game were +// EXCLUDED. If you wish to add them simply follow the example set here. You +// can read MERCPROFILESTRUCT in the file "soldier profile type.h" to see +// all profile data. Add the ones you want to externalize to TEMPPROFILESTRUCT +// in "Soldier Profile.h", and then add the appropriate lines where needed, +// in this file, following my example. +/////////////////////////////////////////////////////////////////////////////// + +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "Debug Control.h" + #include "expat.h" + #include "gamesettings.h" + #include "XML.h" + #include "Soldier Profile.h" +#endif + +//#define MAX_PROFILE_NAME_LENGTH 30 + +struct +{ + PARSE_STAGE curElement; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + TEMPPROFILETYPE curProfile; + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef profileParseData; + +//TEMPPROFILETYPE tempProfiles[NUM_PROFILES+1]; + + + +static void XMLCALL +opinionStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + profileParseData * pData = (profileParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + if(strcmp(name, "MERCOPINIONS") == 0 && pData->curElement == ELEMENT_NONE) + { + pData->curElement = ELEMENT_LIST; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "OPINION") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = ELEMENT; + + //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"MergeStartElementHandle: setting memory for curMerge"); + + memset(&pData->curProfile,0,sizeof(TEMPPROFILETYPE)); + + pData->maxReadDepth++; //we are not skipping this element + + //pData->curIndex++; + } + else if(pData->curElement == ELEMENT && + (strcmp(name, "zNickname") == 0 || + strcmp(name, "uiIndex") == 0 || + strcmp(name, "Opinion0") == 0 || + strcmp(name, "Opinion1") == 0 || + strcmp(name, "Opinion2") == 0 || + strcmp(name, "Opinion3") == 0 || + strcmp(name, "Opinion4") == 0 || + strcmp(name, "Opinion5") == 0 || + strcmp(name, "Opinion6") == 0 || + strcmp(name, "Opinion7") == 0 || + strcmp(name, "Opinion8") == 0 || + strcmp(name, "Opinion9") == 0 || + strcmp(name, "Opinion10") == 0 || + strcmp(name, "Opinion11") == 0 || + strcmp(name, "Opinion12") == 0 || + strcmp(name, "Opinion13") == 0 || + strcmp(name, "Opinion14") == 0 || + strcmp(name, "Opinion15") == 0 || + strcmp(name, "Opinion16") == 0 || + strcmp(name, "Opinion17") == 0 || + strcmp(name, "Opinion18") == 0 || + strcmp(name, "Opinion19") == 0 || + strcmp(name, "Opinion20") == 0 || + strcmp(name, "Opinion21") == 0 || + strcmp(name, "Opinion22") == 0 || + strcmp(name, "Opinion23") == 0 || + strcmp(name, "Opinion24") == 0 || + strcmp(name, "Opinion25") == 0 || + strcmp(name, "Opinion26") == 0 || + strcmp(name, "Opinion27") == 0 || + strcmp(name, "Opinion28") == 0 || + strcmp(name, "Opinion29") == 0 || + strcmp(name, "Opinion30") == 0 || + strcmp(name, "Opinion31") == 0 || + strcmp(name, "Opinion32") == 0 || + strcmp(name, "Opinion33") == 0 || + strcmp(name, "Opinion34") == 0 || + strcmp(name, "Opinion35") == 0 || + strcmp(name, "Opinion36") == 0 || + strcmp(name, "Opinion37") == 0 || + strcmp(name, "Opinion38") == 0 || + strcmp(name, "Opinion39") == 0 || + strcmp(name, "Opinion40") == 0 || + strcmp(name, "Opinion41") == 0 || + strcmp(name, "Opinion42") == 0 || + strcmp(name, "Opinion43") == 0 || + strcmp(name, "Opinion44") == 0 || + strcmp(name, "Opinion45") == 0 || + strcmp(name, "Opinion46") == 0 || + strcmp(name, "Opinion47") == 0 || + strcmp(name, "Opinion48") == 0 || + strcmp(name, "Opinion49") == 0 || + strcmp(name, "Opinion50") == 0 || + strcmp(name, "Opinion51") == 0 || + strcmp(name, "Opinion52") == 0 || + strcmp(name, "Opinion53") == 0 || + strcmp(name, "Opinion54") == 0 || + strcmp(name, "Opinion55") == 0 || + strcmp(name, "Opinion56") == 0 || + strcmp(name, "Opinion57") == 0 || + strcmp(name, "Opinion58") == 0 || + strcmp(name, "Opinion59") == 0 || + strcmp(name, "Opinion60") == 0 || + strcmp(name, "Opinion61") == 0 || + strcmp(name, "Opinion62") == 0 || + strcmp(name, "Opinion63") == 0 || + strcmp(name, "Opinion64") == 0 || + strcmp(name, "Opinion65") == 0 || + strcmp(name, "Opinion66") == 0 || + strcmp(name, "Opinion67") == 0 || + strcmp(name, "Opinion68") == 0 || + strcmp(name, "Opinion69") == 0 || + strcmp(name, "Opinion70") == 0 || + strcmp(name, "Opinion71") == 0 || + strcmp(name, "Opinion72") == 0 || + strcmp(name, "Opinion73") == 0 || + strcmp(name, "Opinion74") == 0 + )) + { + pData->curElement = ELEMENT_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +opinionCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + profileParseData * pData = (profileParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +opinionEndElementHandle(void *userData, const XML_Char *name) +{ + profileParseData * pData = (profileParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "MERCOPINIONS") == 0) + { + pData->curElement = ELEMENT_NONE; + } + else if(strcmp(name, "OPINION") == 0) + { + pData->curElement = ELEMENT_LIST; + + if(pData->curIndex < pData->maxArraySize) + { + + // Write data into a temporary array that holds profiles. We will later copy data from that + // temp array into the REAL profile array, one item at a time, replacing PROF.DAT data. + memcpy( &(tempProfiles[pData->curIndex].bMercOpinion), &(pData->curProfile.bMercOpinion), 75 * sizeof(INT8) ); + + } + } + + else if(strcmp(name, "zNickname") == 0) + { + pData->curElement = ELEMENT; + + // Just a formality + } + + else if(strcmp(name, "uiIndex") == 0) + { + + pData->curElement = ELEMENT; + + // Sets new index for writing. + pData->curIndex = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion0") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[0] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion1") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[1] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion2") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[2] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion3") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[3] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion4") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[4] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion5") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[5] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion6") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[6] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion7") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[7] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion8") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[8] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion9") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[9] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion10") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[10] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion11") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[11] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion12") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[12] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion13") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[13] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion14") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[14] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion15") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[15] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion16") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[16] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion17") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[17] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion18") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[18] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion19") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[19] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion20") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[20] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion21") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[21] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion22") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[22] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion23") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[23] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion24") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[24] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion25") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[25] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion26") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[26] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion27") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[27] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion28") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[28] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion29") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[29] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion30") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[30] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion31") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[31] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion32") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[32] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion33") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[33] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion34") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[34] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion35") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[35] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion36") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[36] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion37") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[37] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion38") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[38] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion39") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[39] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion40") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[40] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion41") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[41] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion42") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[42] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion43") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[43] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion44") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[44] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion45") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[45] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion46") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[46] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion47") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[47] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion48") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[48] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion49") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[49] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion50") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[50] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion51") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[51] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion52") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[52] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion53") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[53] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion54") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[54] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion55") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[55] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion56") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[56] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion57") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[57] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion58") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[58] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion59") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[59] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion60") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[60] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion61") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[61] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion62") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[62] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion63") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[63] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion64") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[64] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion65") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[65] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion66") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[66] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion67") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[67] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion68") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[68] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion69") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[69] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion70") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[70] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion71") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[71] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion72") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[72] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion73") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[73] = (INT8) atol(pData->szCharData); + } + + else if(strcmp(name, "Opinion74") == 0) + { + pData->curElement = ELEMENT; + + pData->curProfile.bMercOpinion[74] = (INT8) atol(pData->szCharData); + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + + + +BOOLEAN ReadInMercOpinions(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + profileParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading MercOpinions.xml" ); + + // Open merges file + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, opinionStartElementHandle, opinionEndElementHandle); + XML_SetCharacterDataHandler(parser, opinionCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + pData.maxArraySize = MAXITEMS; + pData.curIndex = -1; + + XML_SetUserData(parser, &pData); + + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in MercProfiles.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + return( TRUE ); +} + +BOOLEAN WriteMercOpinions() +{ + HWFILE hFile; + + //Debug code; make sure that what we got from the file is the same as what's there + // Open a new file + hFile = FileOpen( "TABLEDATA\\MercOpinions out.xml", FILE_ACCESS_WRITE | FILE_CREATE_ALWAYS, FALSE ); + if ( !hFile ) + return( FALSE ); + + { + UINT32 cnt; + + FilePrintf(hFile,"\r\n"); + for(cnt = 0;cnt < NUM_PROFILES ;cnt++) + { + + FilePrintf(hFile,"\t\r\n"); + + FilePrintf(hFile,"\t\t%d\r\n", cnt); + + ////////////////////////////// + // Write Character's Nickname + + FilePrintf(hFile,"\t\t"); + + STR16 szRemainder = gMercProfiles[cnt].zNickname; //the remaining string to be output (for making valid XML) + + while(szRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\0"); + CHAR16 invChar = szRemainder[uiCharLoc]; + + if(uiCharLoc) + { + szRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%S",szRemainder); + szRemainder[uiCharLoc] = invChar; + } + + szRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + szRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + szRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + szRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + + UINT8 cnt_b = 0; + for (cnt_b = 0; cnt_b < 75; cnt_b++) + { + FilePrintf(hFile,"\t\t%d\r\n", cnt_b, gMercProfiles[ cnt ].bMercOpinion[cnt_b], cnt_b); + } + + + FilePrintf(hFile,"\t\r\n"); + } + FilePrintf(hFile,"\r\n"); + } + FileClose( hFile ); + + return( TRUE ); +} diff --git a/Tactical/XML_Profiles.cpp b/Tactical/XML_Profiles.cpp new file mode 100644 index 00000000..189eaaec --- /dev/null +++ b/Tactical/XML_Profiles.cpp @@ -0,0 +1,1190 @@ +/////////////////////////////////////////////////////////////////////////////// +// HEADROCK PROFEX: PROFile EXternalization +// +// This file handles all reading from Merc Profiles.XML. It offers an external +// alternative to PROEDIT. Values that have no current use in the game were +// EXCLUDED. If you wish to add them simply follow the example set here. You +// can read MERCPROFILESTRUCT in the file "soldier profile type.h" to see +// all profile data. Add the ones you want to externalize to TEMPPROFILESTRUCT +// in "Soldier Profile.h", and then add the appropriate lines where needed, +// in this file, following my example. +/////////////////////////////////////////////////////////////////////////////// + +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "Debug Control.h" + #include "expat.h" + #include "gamesettings.h" + #include "XML.h" + #include "Soldier Profile.h" +#endif + +//#define MAX_PROFILE_NAME_LENGTH 30 + +struct +{ + PARSE_STAGE curElement; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + TEMPPROFILETYPE curProfile; + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef profileParseData; + +TEMPPROFILETYPE tempProfiles[NUM_PROFILES+1]; + + + +static void XMLCALL +profileStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + profileParseData * pData = (profileParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + if(strcmp(name, "MERCPROFILES") == 0 && pData->curElement == ELEMENT_NONE) + { + pData->curElement = ELEMENT_LIST; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "PROFILE") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = ELEMENT; + + //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"MergeStartElementHandle: setting memory for curMerge"); + + memset(&pData->curProfile,0,sizeof(TEMPPROFILETYPE)); + + pData->maxReadDepth++; //we are not skipping this element + + //pData->curIndex++; + } + else if(pData->curElement == ELEMENT && + (strcmp(name, "zName") == 0 || + strcmp(name, "zNickname") == 0 || + strcmp(name, "uiIndex") == 0 || + strcmp(name, "ubFaceIndex") == 0 || + strcmp(name, "usEyesX") == 0 || + strcmp(name, "usEyesY") == 0 || + strcmp(name, "usMouthX") == 0 || + strcmp(name, "usMouthY") == 0 || + strcmp(name, "uiEyeDelay") == 0 || + strcmp(name, "uiMouthDelay") == 0 || + strcmp(name, "uiBlinkFrequency") == 0 || + strcmp(name, "uiExpressionFrequency") == 0 || + strcmp(name, "PANTS") == 0 || + strcmp(name, "VEST") == 0 || + strcmp(name, "SKIN") == 0 || + strcmp(name, "HAIR") == 0 || + strcmp(name, "bSex") == 0 || + strcmp(name, "ubBodyType") == 0 || + strcmp(name, "uiBodyTypeSubFlags") == 0 || + strcmp(name, "bAttitude") == 0 || + strcmp(name, "bPersonalityTrait") == 0 || + strcmp(name, "ubNeedForSleep") == 0 || + strcmp(name, "bReputationTolerance") == 0 || + strcmp(name, "bDeathRate") == 0 || + strcmp(name, "bLifeMax") == 0 || + strcmp(name, "bLife") == 0 || + strcmp(name, "bStrength") == 0 || + strcmp(name, "bAgility") == 0 || + strcmp(name, "bDexterity") == 0 || + strcmp(name, "bWisdom") == 0 || + strcmp(name, "bMarksmanship") == 0 || + strcmp(name, "bExplosive") == 0 || + strcmp(name, "bLeadership") == 0 || + strcmp(name, "bMedical") == 0 || + strcmp(name, "bMechanical") == 0 || + strcmp(name, "bExpLevel") == 0 || + strcmp(name, "bEvolution") == 0 || + strcmp(name, "bSkillTrait") == 0 || + strcmp(name, "bSkillTrait2") == 0 || + strcmp(name, "bBuddy1") == 0 || + strcmp(name, "bBuddy2") == 0 || + strcmp(name, "bBuddy3") == 0 || + strcmp(name, "bBuddy4") == 0 || + strcmp(name, "bBuddy5") == 0 || + strcmp(name, "bLearnToLike") == 0 || + strcmp(name, "bLearnToLikeTime") == 0 || + strcmp(name, "bHated1") == 0 || + strcmp(name, "bHatedTime1") == 0 || + strcmp(name, "bHated2") == 0 || + strcmp(name, "bHatedTime2") == 0 || + strcmp(name, "bHated3") == 0 || + strcmp(name, "bHatedTime3") == 0 || + strcmp(name, "bHated4") == 0 || + strcmp(name, "bHatedTime4") == 0 || + strcmp(name, "bHated5") == 0 || + strcmp(name, "bHatedTime5") == 0 || + strcmp(name, "bLearnToHate") == 0 || + strcmp(name, "bLearnToHateTime") == 0 || + strcmp(name, "sSalary") == 0 || + strcmp(name, "uiWeeklySalary") == 0 || + strcmp(name, "uiBiWeeklySalary") == 0 || + strcmp(name, "bMedicalDeposit") == 0 || + strcmp(name, "sMedicalDepositAmount") == 0 || + strcmp(name, "usOptionalGearCost") == 0 || + strcmp(name, "bArmourAttractiveness") == 0 || + strcmp(name, "bMainGunAttractiveness") == 0 || + strcmp(name, "fGoodGuy") == 0 || + strcmp(name, "usApproachFactorFriendly") == 0 || + strcmp(name, "usApproachFactorDirect") == 0 || + strcmp(name, "usApproachFactorThreaten") == 0 || + strcmp(name, "usApproachFactorRecruit") == 0 + )) + { + pData->curElement = ELEMENT_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +profileCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + profileParseData * pData = (profileParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +profileEndElementHandle(void *userData, const XML_Char *name) +{ + profileParseData * pData = (profileParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "MERCPROFILES") == 0) + { + pData->curElement = ELEMENT_NONE; + } + else if(strcmp(name, "PROFILE") == 0) + { + pData->curElement = ELEMENT_LIST; + + if(pData->curIndex < pData->maxArraySize) + { + + // Write data into a temporary array that holds profiles. We will later copy data from that + // temp array into the REAL profile array, one item at a time, replacing PROF.DAT data. + wcscpy(tempProfiles[pData->curIndex].zName, pData->curProfile.zName); + wcscpy(tempProfiles[pData->curIndex].zNickname, pData->curProfile.zNickname); + + tempProfiles[pData->curIndex].ubFaceIndex = pData->curProfile.ubFaceIndex; + tempProfiles[pData->curIndex].usEyesX = pData->curProfile.usEyesX; + tempProfiles[pData->curIndex].usEyesY = pData->curProfile.usEyesY; + tempProfiles[pData->curIndex].usMouthX = pData->curProfile.usMouthX; + tempProfiles[pData->curIndex].usMouthY = pData->curProfile.usMouthY; + tempProfiles[pData->curIndex].uiEyeDelay = pData->curProfile.uiEyeDelay; + tempProfiles[pData->curIndex].uiMouthDelay = pData->curProfile.uiMouthDelay; + tempProfiles[pData->curIndex].uiBlinkFrequency = pData->curProfile.uiBlinkFrequency; + tempProfiles[pData->curIndex].uiExpressionFrequency = pData->curProfile.uiExpressionFrequency; + + memcpy( &(tempProfiles[pData->curIndex].PANTS), &(pData->curProfile.PANTS), sizeof(PaletteRepID) ); + memcpy( &(tempProfiles[pData->curIndex].VEST), &(pData->curProfile.VEST), sizeof(PaletteRepID) ); + memcpy( &(tempProfiles[pData->curIndex].SKIN), &(pData->curProfile.SKIN), sizeof(PaletteRepID) ); + memcpy( &(tempProfiles[pData->curIndex].HAIR), &(pData->curProfile.HAIR), sizeof(PaletteRepID) ); + + tempProfiles[pData->curIndex].bSex = pData->curProfile.bSex; + tempProfiles[pData->curIndex].ubBodyType = pData->curProfile.ubBodyType; + tempProfiles[pData->curIndex].uiBodyTypeSubFlags = pData->curProfile.uiBodyTypeSubFlags; + + tempProfiles[pData->curIndex].bAttitude = pData->curProfile.bAttitude; + tempProfiles[pData->curIndex].bPersonalityTrait = pData->curProfile.bPersonalityTrait; + tempProfiles[pData->curIndex].ubNeedForSleep = pData->curProfile.ubNeedForSleep; + tempProfiles[pData->curIndex].bReputationTolerance = pData->curProfile.bReputationTolerance; + tempProfiles[pData->curIndex].bDeathRate = pData->curProfile.bDeathRate; + + tempProfiles[pData->curIndex].bLifeMax = pData->curProfile.bLifeMax; + tempProfiles[pData->curIndex].bLife = pData->curProfile.bLife; + tempProfiles[pData->curIndex].bStrength = pData->curProfile.bStrength; + tempProfiles[pData->curIndex].bAgility = pData->curProfile.bAgility; + tempProfiles[pData->curIndex].bDexterity = pData->curProfile.bDexterity; + tempProfiles[pData->curIndex].bWisdom = pData->curProfile.bWisdom; + tempProfiles[pData->curIndex].bMarksmanship = pData->curProfile.bMarksmanship; + tempProfiles[pData->curIndex].bExplosive = pData->curProfile.bExplosive; + tempProfiles[pData->curIndex].bLeadership = pData->curProfile.bLeadership; + tempProfiles[pData->curIndex].bMedical = pData->curProfile.bMedical; + tempProfiles[pData->curIndex].bMechanical = pData->curProfile.bMechanical; + tempProfiles[pData->curIndex].bExpLevel = pData->curProfile.bExpLevel; + + tempProfiles[pData->curIndex].bEvolution = pData->curProfile.bEvolution; + tempProfiles[pData->curIndex].bSkillTrait = pData->curProfile.bSkillTrait; + tempProfiles[pData->curIndex].bSkillTrait2 = pData->curProfile.bSkillTrait2; + + memcpy( &(tempProfiles[pData->curIndex].bBuddy), &(pData->curProfile.bBuddy), 5 * sizeof (INT8)); + tempProfiles[pData->curIndex].bLearnToLike = pData->curProfile.bLearnToLike; + tempProfiles[pData->curIndex].bLearnToLikeTime = pData->curProfile.bLearnToLikeTime; + + memcpy( &(tempProfiles[pData->curIndex].bHated), &(pData->curProfile.bHated), 5 * sizeof (INT8)); + memcpy( &(tempProfiles[pData->curIndex].bHatedTime), &(pData->curProfile.bHatedTime), 5 * sizeof (INT8)); + tempProfiles[pData->curIndex].bLearnToHate = pData->curProfile.bLearnToHate; + tempProfiles[pData->curIndex].bLearnToHateTime = pData->curProfile.bLearnToHateTime; + + tempProfiles[pData->curIndex].sSalary = pData->curProfile.sSalary; + tempProfiles[pData->curIndex].uiWeeklySalary = pData->curProfile.uiWeeklySalary; + tempProfiles[pData->curIndex].uiBiWeeklySalary = pData->curProfile.uiBiWeeklySalary; + tempProfiles[pData->curIndex].bMedicalDeposit = pData->curProfile.bMedicalDeposit; + tempProfiles[pData->curIndex].sMedicalDepositAmount = pData->curProfile.sMedicalDepositAmount; + tempProfiles[pData->curIndex].usOptionalGearCost = pData->curProfile.usOptionalGearCost; + + tempProfiles[pData->curIndex].bArmourAttractiveness = pData->curProfile.bArmourAttractiveness; + tempProfiles[pData->curIndex].bMainGunAttractiveness = pData->curProfile.bMainGunAttractiveness; + + tempProfiles[pData->curIndex].fGoodGuy = pData->curProfile.fGoodGuy; + memcpy( &(tempProfiles[pData->curIndex].usApproachFactor), &(pData->curProfile.usApproachFactor), 4 * sizeof (UINT16)); + } + } + + else if(strcmp(name, "zName") == 0) + { + pData->curElement = ELEMENT; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curProfile.zName, sizeof(pData->curProfile.zName)/sizeof(pData->curProfile.zName[0]) ); + pData->curProfile.zName[sizeof(pData->curProfile.zName)/sizeof(pData->curProfile.zName[0]) - 1] = '\0'; + } + + else if(strcmp(name, "zNickname") == 0) + { + pData->curElement = ELEMENT; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curProfile.zNickname, sizeof(pData->curProfile.zNickname)/sizeof(pData->curProfile.zNickname[0]) ); + pData->curProfile.zNickname[sizeof(pData->curProfile.zNickname)/sizeof(pData->curProfile.zNickname[0]) - 1] = '\0'; + } + + else if(strcmp(name, "ubFaceIndex") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.ubFaceIndex = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usEyesX") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usEyesX = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usEyesY") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usEyesY = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usMouthX") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usMouthX = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usMouthY") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usMouthY = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiEyeDelay") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiEyeDelay = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiMouthDelay") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiMouthDelay = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiBlinkFrequency") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiBlinkFrequency = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiExpressionFrequency") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiExpressionFrequency = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "PANTS") == 0) + { + pData->curElement = ELEMENT; + strcpy(pData->curProfile.PANTS, pData->szCharData); + } + + else if(strcmp(name, "VEST") == 0) + { + pData->curElement = ELEMENT; + strcpy(pData->curProfile.VEST, pData->szCharData); + } + + else if(strcmp(name, "SKIN") == 0) + { + pData->curElement = ELEMENT; + strcpy(pData->curProfile.SKIN, pData->szCharData); + } + + else if(strcmp(name, "HAIR") == 0) + { + pData->curElement = ELEMENT; + strcpy(pData->curProfile.HAIR, pData->szCharData); + } + + else if(strcmp(name, "bSex") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bSex = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "ubBodyType") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.ubBodyType = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiBodyTypeSubFlags") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiBodyTypeSubFlags = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bAttitude") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bAttitude = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bPersonalityTrait") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bPersonalityTrait = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "ubNeedForSleep") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.ubNeedForSleep = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bReputationTolerance") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bReputationTolerance = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bDeathRate") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bDeathRate = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLifeMax") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLifeMax = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLife") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLife = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bStrength") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bStrength = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bAgility") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bAgility = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bDexterity") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bDexterity = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bWisdom") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bWisdom = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bMarksmanship") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bMarksmanship = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bExplosive") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bExplosive = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLeadership") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLeadership = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bMedical") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bMedical = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bMechanical") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bMechanical = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bExpLevel") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bExpLevel = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bEvolution") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bEvolution = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bSkillTrait") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bSkillTrait = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bSkillTrait2") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bSkillTrait2 = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bBuddy1") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bBuddy[0] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bBuddy2") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bBuddy[1] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bBuddy3") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bBuddy[2] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bBuddy4") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bBuddy[3] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bBuddy5") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bBuddy[4] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLearnToLike") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLearnToLike = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLearnToLikeTime") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLearnToLikeTime = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHated1") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHated[0] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHatedTime1") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHatedTime[0] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHated2") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHated[1] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHatedTime2") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHatedTime[1] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHated3") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHated[2] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHatedTime3") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHatedTime[2] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHated4") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHated[3] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHatedTime4") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHatedTime[3] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHated5") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHated[4] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bHatedTime5") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bHatedTime[4] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLearnToHate") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLearnToHate = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bLearnToHateTime") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bLearnToHateTime = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "sSalary") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.sSalary = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiWeeklySalary") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiWeeklySalary = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiBiWeeklySalary") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.uiBiWeeklySalary = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bMedicalDeposit") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bMedicalDeposit = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "sMedicalDepositAmount") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.sMedicalDepositAmount = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usOptionalGearCost") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usOptionalGearCost = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bArmourAttractiveness") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bArmourAttractiveness = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "bMainGunAttractiveness") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.bMainGunAttractiveness = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "fGoodGuy") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.fGoodGuy = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usApproachFactorFriendly") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usApproachFactor[0] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usApproachFactorDirect") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usApproachFactor[1] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usApproachFactorThreaten") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usApproachFactor[2] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "usApproachFactorRecruit") == 0) + { + pData->curElement = ELEMENT; + pData->curProfile.usApproachFactor[3] = (UINT32) atol(pData->szCharData); + } + + else if(strcmp(name, "uiIndex") == 0) + { + + pData->curElement = ELEMENT; + + // Sets new index for writing. + pData->curIndex = (UINT32) atol(pData->szCharData); + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + + + +BOOLEAN ReadInMercProfiles(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + profileParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading MercProfiles.xml" ); + + // Open merges file + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, profileStartElementHandle, profileEndElementHandle); + XML_SetCharacterDataHandler(parser, profileCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + pData.maxArraySize = MAXITEMS; + pData.curIndex = -1; + + XML_SetUserData(parser, &pData); + + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in MercProfiles.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + return( TRUE ); +} + +BOOLEAN WriteMercProfiles() +{ + HWFILE hFile; + + //Debug code; make sure that what we got from the file is the same as what's there + // Open a new file + hFile = FileOpen( "TABLEDATA\\MercProfiles out.xml", FILE_ACCESS_WRITE | FILE_CREATE_ALWAYS, FALSE ); + if ( !hFile ) + return( FALSE ); + + { + UINT32 cnt; + + FilePrintf(hFile,"\r\n"); + for(cnt = 0;cnt < NUM_PROFILES ;cnt++) + { + + FilePrintf(hFile,"\t\r\n"); + + FilePrintf(hFile,"\t\t%d\r\n", cnt); + + ////////////////////////////// + // Write Character Name + + FilePrintf(hFile,"\t\t"); + + STR16 szRemainder = gMercProfiles[cnt].zName; //the remaining string to be output (for making valid XML) + + while(szRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\0"); + CHAR16 invChar = szRemainder[uiCharLoc]; + + if(uiCharLoc) + { + szRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%S",szRemainder); + szRemainder[uiCharLoc] = invChar; + } + + szRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + szRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + szRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + szRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + + ////////////////////////////// + // Write Character Nickname + + FilePrintf(hFile,"\t\t"); + + szRemainder = gMercProfiles[cnt].zNickname; //the remaining string to be output (for making valid XML) + + while(szRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\0"); + CHAR16 invChar = szRemainder[uiCharLoc]; + + if(uiCharLoc) + { + szRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%S",szRemainder); + szRemainder[uiCharLoc] = invChar; + } + + szRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + szRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + szRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + szRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + // + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].ubFaceIndex); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usEyesX); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usEyesY); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usMouthX); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usMouthY); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiEyeDelay); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiMouthDelay); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiBlinkFrequency); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiExpressionFrequency); + + ////////////////////////////// + // Pant Color + + FilePrintf(hFile,"\t\t"); + + STR8 zRemainder = gMercProfiles[cnt].PANTS; + //strcpy(zRemainder, gMercProfiles[cnt].PANTS); //the remaining string to be output (for making valid XML) + + while(zRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = strcspn(zRemainder,"&<>\0"); + CHAR8 invChar = zRemainder[uiCharLoc]; + + if(uiCharLoc) + { + zRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%s",zRemainder); + zRemainder[uiCharLoc] = invChar; + } + + zRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + zRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + zRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + zRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + + ////////////////////////////// + // Vest Color + + FilePrintf(hFile,"\t\t"); + + zRemainder = gMercProfiles[cnt].VEST; + //strcpy(zRemainder, gMercProfiles[cnt].VEST); //the remaining string to be output (for making valid XML) + + while(zRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = strcspn(zRemainder,"&<>\0"); + CHAR8 invChar = zRemainder[uiCharLoc]; + + if(uiCharLoc) + { + zRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%s",zRemainder); + zRemainder[uiCharLoc] = invChar; + } + + zRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + zRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + zRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + zRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + + ////////////////////////////// + // Pant Color + + FilePrintf(hFile,"\t\t"); + + zRemainder = gMercProfiles[cnt].SKIN; + //strcpy(zRemainder,gMercProfiles[cnt].SKIN); //the remaining string to be output (for making valid XML) + + while(zRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = strcspn(zRemainder,"&<>\0"); + CHAR8 invChar = zRemainder[uiCharLoc]; + + if(uiCharLoc) + { + zRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%s",zRemainder); + zRemainder[uiCharLoc] = invChar; + } + + zRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + zRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + zRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + zRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + + ////////////////////////////// + // Pant Color + + FilePrintf(hFile,"\t\t"); + + zRemainder = gMercProfiles[cnt].HAIR; + //strcpy(zRemainder,gMercProfiles[cnt].HAIR); //the remaining string to be output (for making valid XML) + + while(zRemainder[0] != '\0') + { + //UINT32 uiCharLoc = wcscspn(szRemainder,L"&<>\'\"\0"); + UINT32 uiCharLoc = strcspn(zRemainder,"&<>\0"); + CHAR8 invChar = zRemainder[uiCharLoc]; + + if(uiCharLoc) + { + zRemainder[uiCharLoc] = '\0'; + FilePrintf(hFile,"%s",zRemainder); + zRemainder[uiCharLoc] = invChar; + } + + zRemainder += uiCharLoc; + + switch(invChar) + { + case '&': + FilePrintf(hFile,"&"); + zRemainder++; + break; + + case '<': + FilePrintf(hFile,"<"); + zRemainder++; + break; + + case '>': + FilePrintf(hFile,">"); + zRemainder++; + break; + + //case '\'': + // FilePrintf(hFile,"'"); + // szRemainder++; + //break; + + //case '\"': + // FilePrintf(hFile,"""); + // szRemainder++; + //break; + } + } + + FilePrintf(hFile,"\r\n"); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bSex); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].ubBodyType); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiBodyTypeSubFlags); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bAttitude); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bPersonalityTrait); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].ubNeedForSleep); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bReputationTolerance); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bDeathRate); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLifeMax); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLife); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bStrength); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bAgility); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bDexterity); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bWisdom); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMarksmanship); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bExplosive); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLeadership); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMedical); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMechanical); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bExpLevel); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bEvolution); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bSkillTrait); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bSkillTrait2); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bBuddy[0]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bBuddy[1]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bBuddy[2]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bBuddy[3]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bBuddy[4]); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLearnToLike); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLearnToLikeTime); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHated[0]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHatedTime[0]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHated[1]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHatedTime[1]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHated[2]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHatedTime[2]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHated[3]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHatedTime[3]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHated[4]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bHatedTime[4]); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLearnToHate); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bLearnToHateTime); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].sSalary); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiWeeklySalary); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].uiBiWeeklySalary); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMedicalDeposit); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].sMedicalDepositAmount); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usOptionalGearCost); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bArmourAttractiveness); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].bMainGunAttractiveness); + + FilePrintf(hFile,"\t\t%d\r\n", ((gMercProfiles[ cnt ].ubMiscFlags3 & PROFILE_MISC_FLAG3_GOODGUY) != 0)); + + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usApproachFactor[0]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usApproachFactor[1]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usApproachFactor[2]); + FilePrintf(hFile,"\t\t%d\r\n", gMercProfiles[ cnt ].usApproachFactor[3]); + + + FilePrintf(hFile,"\t\r\n"); + } + FilePrintf(hFile,"\r\n"); + } + FileClose( hFile ); + + return( TRUE ); +} diff --git a/Tactical/XML_Profiles.h b/Tactical/XML_Profiles.h new file mode 100644 index 00000000..e69de29b diff --git a/Tactical/XML_SpreadPatterns.cpp b/Tactical/XML_SpreadPatterns.cpp index c01d1a21..bb4960ac 100644 --- a/Tactical/XML_SpreadPatterns.cpp +++ b/Tactical/XML_SpreadPatterns.cpp @@ -396,4 +396,4 @@ int FindSpreadPatternIndex( const STR strName ) } return n; -} +} \ No newline at end of file diff --git a/Tactical/bullets.cpp b/Tactical/bullets.cpp index be9da9ba..a3ce5207 100644 --- a/Tactical/bullets.cpp +++ b/Tactical/bullets.cpp @@ -474,7 +474,7 @@ void AddMissileTrail( BULLET *pBullet, FIXEDPT qCurrX, FIXEDPT qCurrY, FIXEDPT q // 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 ((gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.ubRealisticTracers == 0 && fTracer == TRUE)) { if ( pBullet->iLoop < 5 ) { @@ -525,7 +525,7 @@ void AddMissileTrail( BULLET *pBullet, FIXEDPT qCurrX, FIXEDPT qCurrY, FIXEDPT q // 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)) + else if ((gGameExternalOptions.ubRealisticTracers > 0 && gGameExternalOptions.ubNumBulletsPerTracer > 0 && pBullet->fTracer == TRUE) || (gGameExternalOptions.ubRealisticTracers == 0 && fTracer == TRUE)) { INT16 sXPos, sYPos; diff --git a/Tactical/opplist.cpp b/Tactical/opplist.cpp index 5229d20b..ff8d1c14 100644 --- a/Tactical/opplist.cpp +++ b/Tactical/opplist.cpp @@ -58,7 +58,8 @@ #define WE_SEE_WHAT_MILITIA_SEES_AND_VICE_VERSA extern void SetSoldierAniSpeed( SOLDIERTYPE *pSoldier ); -void MakeBloodcatsHostile( void ); +// HEADROCK HAM 3.6: Moved to header +//void MakeBloodcatsHostile( void ); void OurNoise( UINT8 ubNoiseMaker, INT16 sGridNo, INT8 bLevel, UINT8 ubTerrType, UINT8 ubVolume, UINT8 ubNoiseType ); void TheirNoise(UINT8 ubNoiseMaker, INT16 sGridNo, INT8 bLevel, UINT8 ubTerrType, UINT8 ubVolume, UINT8 ubNoiseType ); @@ -490,7 +491,9 @@ void HandleBestSightingPositionInRealtime( void ) // get rid of the item under cursor (we gotta react FAST) CancelItemPointer(); // select (and center screen on) the merc who saw the enemy - if (gusSelectedSoldier != (UINT16)MercPtrs[gubBestToMakeSighting[ 0 ]]->ubID) + // HEADROCK HAM 3.6: A much-requested toggle. + if (gusSelectedSoldier != (UINT16)MercPtrs[gubBestToMakeSighting[ 0 ]]->ubID && + !gGameExternalOptions.fNoAutoFocusChangeInRealtimeSneak) SelectSoldier (MercPtrs[gubBestToMakeSighting[ 0 ]]->ubID, false, true); // if not quiet, emit a message warning the player if (!gGameExternalOptions.fQuietRealTimeSneak) @@ -1251,6 +1254,19 @@ INT16 DistanceVisible( SOLDIERTYPE *pSoldier, INT8 bFacingDir, INT8 bSubjectDir, if (!sideViewLimit) { sDistVisible += sDistVisible * GetTotalVisionRangeBonus(pSoldier, bLightLevel) / 100; + + // HEADROCK HAM 3.2: Further reduce sightrange for cowering characters. + if (gGameExternalOptions.ubCoweringReducesSightRange == 1 || gGameExternalOptions.ubCoweringReducesSightRange == 2) + { + INT8 bTolerance = CalcSuppressionTolerance( pSoldier ); + + // Make sure character is cowering. + if ( pSoldier->aiData.bShock >= bTolerance && gGameExternalOptions.ubMaxSuppressionShock > 0 && + sDistVisible > 0 ) + { + sDistVisible = __max(1,(sDistVisible * (gGameExternalOptions.ubMaxSuppressionShock - pSoldier->aiData.bShock)) / gGameExternalOptions.ubMaxSuppressionShock); + } + } } @@ -2299,7 +2315,13 @@ void ManSeesMan(SOLDIERTYPE *pSoldier, SOLDIERTYPE *pOpponent, INT16 sOppGridno, } else if ( pOpponent->ubBodyType == BLOODCAT && pOpponent->aiData.bNeutral) { - MakeBloodcatsHostile(); + // HEADROCK HAM 3.6: If bloodcats are set as affiliated with civilians, do not trigger hostilities. + if ( gBloodcatPlacements[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][ 0 ].PlacementType != BLOODCAT_PLACEMENT_STATIC || + gBloodcatPlacements[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][ gGameOptions.ubDifficultyLevel - 1 ].ubFactionAffiliation == NON_CIV_GROUP || + gBloodcatPlacements[SECTOR(pSoldier->sSectorX, pSoldier->sSectorY)][ gGameOptions.ubDifficultyLevel - 1 ].ubFactionAffiliation == QUEENS_CIV_GROUP ) + { + MakeBloodcatsHostile(); + } /* SetSoldierNonNeutral( pOpponent ); RecalculateOppCntsDueToNoLongerNeutral( pOpponent ); @@ -5608,20 +5630,44 @@ void ProcessNoise(UINT8 ubNoiseMaker, INT16 sGridNo, INT8 bLevel, UINT8 ubTerrTy break; } - if ( gWorldSectorX == 5 && gWorldSectorY == MAP_ROW_N ) + // HEADROCK HAM 3.6: Bloodcat "static" sectors have been externalized, and there can be more than one. + // Also, there's a toggle that determines whether or not bloodcats can sense enemies in this sector. + UINT8 ubSectorID = SECTOR(gWorldSectorX, gWorldSectorY); + UINT8 PlacementType = gBloodcatPlacements[ ubSectorID ][0].PlacementType; + + if (PlacementType == BLOODCAT_PLACEMENT_STATIC) { - // in the bloodcat arena sector, skip noises between army & bloodcats - if ( pSoldier->bTeam == ENEMY_TEAM && MercPtrs[ ubNoiseMaker ]->bTeam == CREATURE_TEAM ) + if (gBloodcatPlacements[ ubSectorID ][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation == QUEENS_CIV_GROUP) { - continue; + // skip noises between army & bloodcats + if ( pSoldier->bTeam == ENEMY_TEAM && MercPtrs[ ubNoiseMaker ]->ubBodyType == BLOODCAT && MercPtrs[ ubNoiseMaker ]->bTeam == CREATURE_TEAM ) + { + continue; + } + if ( pSoldier->bTeam == CREATURE_TEAM && pSoldier->ubBodyType == BLOODCAT && MercPtrs[ ubNoiseMaker ]->bTeam == ENEMY_TEAM ) + { + continue; + } } - if ( pSoldier->bTeam == CREATURE_TEAM && MercPtrs[ ubNoiseMaker ]->bTeam == ENEMY_TEAM ) + else if (gBloodcatPlacements[ ubSectorID ][ gGameOptions.ubDifficultyLevel-1 ].ubFactionAffiliation > NON_CIV_GROUP) { - continue; + if ( MercPtrs[ ubNoiseMaker ]->ubBodyType == BLOODCAT && MercPtrs[ ubNoiseMaker ]->bTeam == CREATURE_TEAM && pSoldier->bSide != gbPlayerNum) + { + // Target is a bloodcat. He can't be heard by civilians no matter what. + { + continue; + } + } + else if ( pSoldier->bTeam == CREATURE_TEAM && pSoldier->ubBodyType == BLOODCAT ) + { + // Source is a bloodcat. He can only hear player-side soldiers, and only if hostile. + if ( MercPtrs[ ubNoiseMaker ]->bSide != gbPlayerNum || pSoldier->aiData.bNeutral ) + { + continue; + } + } } } - - } else { @@ -7253,12 +7299,12 @@ void MakeBloodcatsHostile( void ) { if ( pSoldier->ubBodyType == BLOODCAT && pSoldier->bActive && pSoldier->bInSector && pSoldier->stats.bLife > 0 ) { - SetSoldierNonNeutral( pSoldier ); - RecalculateOppCntsDueToNoLongerNeutral( pSoldier ); - if ( ( gTacticalStatus.uiFlags & INCOMBAT ) ) - { - CheckForPotentialAddToBattleIncrement( pSoldier ); - } + SetSoldierNonNeutral( pSoldier ); + RecalculateOppCntsDueToNoLongerNeutral( pSoldier ); + if ( ( gTacticalStatus.uiFlags & INCOMBAT ) ) + { + CheckForPotentialAddToBattleIncrement( pSoldier ); + } } } diff --git a/Tactical/opplist.h b/Tactical/opplist.h index 74f8a5f0..227eeb08 100644 --- a/Tactical/opplist.h +++ b/Tactical/opplist.h @@ -151,4 +151,6 @@ extern INT8 gbLightSighting[1][16]; BOOLEAN SoldierHasLimitedVision(SOLDIERTYPE * pSoldier); +// HEADROCK HAM 3.6: Moved here from cpp +void MakeBloodcatsHostile( void ); #endif \ No newline at end of file diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index c7a874f8..63a3afcf 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -1298,7 +1298,7 @@ void CalcBestStab(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab, BOOLEAN fBladeAt else // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI // ubChanceToHit = MAXCHANCETOHIT; - ubChanceToHit = gGameExternalOptions.iMaximumCTH; + ubChanceToHit = gGameExternalOptions.ubMaximumCTH; //NumMessage("chance to Hit = ",ubChanceToHit); //sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit); @@ -1477,7 +1477,7 @@ void CalcTentacleAttack(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestStab ) else // HEADROCK (HAM): Externalized maximum to JA2_OPTIONS.INI // ubChanceToHit = MAXCHANCETOHIT; - ubChanceToHit = gGameExternalOptions.iMaximumCTH; + ubChanceToHit = gGameExternalOptions.ubMaximumCTH; //NumMessage("chance to Hit = ",ubChanceToHit); //sprintf(tempstr,"Vs. %s, at AimTime %d, ubChanceToHit = %d",ExtMen[pOpponent->ubID].name,ubAimTime,ubChanceToHit); @@ -1886,8 +1886,10 @@ INT8 TryToReload( SOLDIERTYPE * pSoldier ) WEAPONTYPE *pWeapon; OBJECTTYPE *pObj, *pObj2; + // HEADROCK HAM 3.3: Attempt to reload now takes magazine type into account. Prefernace will be given to magazines of similar type. + pObj = &(pSoldier->inv[HANDPOS]); pWeapon = &(Weapon[pSoldier->inv[HANDPOS].usItem]); - bSlot = FindAmmo( pSoldier, pWeapon->ubCalibre, pWeapon->ubMagSize, NO_SLOT ); + bSlot = FindAmmo( pSoldier, pWeapon->ubCalibre, pWeapon->ubMagSize, GetAmmoType(pObj), NO_SLOT ); //if (bSlot != NO_SLOT) //{ @@ -1898,7 +1900,7 @@ INT8 TryToReload( SOLDIERTYPE * pSoldier ) //} // manual recharge - pObj = &(pSoldier->inv[HANDPOS]); + //pObj = &(pSoldier->inv[HANDPOS]); if ((*pObj)[0]->data.gun.ubGunShotsLeft && !((*pObj)[0]->data.gun.ubGunState & GS_CARTRIDGE_IN_CHAMBER) ) { diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 5617816f..0362ea4a 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -738,9 +738,7 @@ INT8 DecideActionGreen(SOLDIERTYPE *pSoldier) // character then didn't trigger the end of boxing record // (and we know from the if statement above that we're // still in a boxing state of some sort...) - //TriggerEndOfBoxingRecord( NULL ); - - // HEADROCK HAM 3.6: This should trigger Darren's script + // HEADROCK HAM 3.6: The NULL here causes issues of non-payment! TriggerEndOfBoxingRecord( pSoldier ); } } @@ -2247,7 +2245,10 @@ if(!is_networked)//hayden // then call for spotters! Uses up the rest of his turn (whatever // that may be), but from now on, BLACK AI NPC may radio sightings! gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID; - pSoldier->bActionPoints = 0; + // HEADROCK HAM 3.1: This may be causing problems with HAM's lowered AP limit. From now on, we'll check + // whether the soldier has more than 0 APs to begin with. + if (pSoldier->bActionPoints > 0) + pSoldier->bActionPoints = 0; #ifdef DEBUGDECISIONS AINameMessage(pSoldier,"calls for spotters!",1000); @@ -2304,7 +2305,10 @@ if(!is_networked)//hayden // then call for spotters! Uses up the rest of his turn (whatever // that may be), but from now on, BLACK AI NPC may radio sightings! gTacticalStatus.ubSpottersCalledForBy = pSoldier->ubID; - pSoldier->bActionPoints = 0; + // HEADROCK HAM 3.1: This may be causing problems with HAM's lowered AP limit. From now on, we'll check + // whether the soldier has more than 0 APs to begin with. + if (pSoldier->bActionPoints > 0) + pSoldier->bActionPoints = 0; DebugMsg (TOPIC_JA2,DBG_LEVEL_3,"decideactionred: calling for sniper spotters"); @@ -3161,7 +3165,7 @@ if(!is_networked)//hayden // is terribly important if Suppression Shock is enabled. UINT16 bShock = 0; - if (gGameExternalOptions.fSuppressionShock) + if (gGameExternalOptions.usSuppressionShockEffect > 0 ) { // If bShock value is greater than (2*ExpLevel + MoraleModifier)*1.5, the target will flee. bShock = pSoldier->aiData.bShock; diff --git a/TacticalAI/Movement.cpp b/TacticalAI/Movement.cpp index 1da435d8..d963a50d 100644 --- a/TacticalAI/Movement.cpp +++ b/TacticalAI/Movement.cpp @@ -849,7 +849,7 @@ void SoldierTriesToContinueAlongPath(SOLDIERTYPE *pSoldier) // Find out how much it takes to move here! bAPCost = EstimateActionPointCost( pSoldier, sNewGridNo, (INT8)pSoldier->pathing.usPathingData[ pSoldier->pathing.usPathIndex ], pSoldier->usUIMovementMode, (INT8) pSoldier->pathing.usPathIndex, (INT8) pSoldier->pathing.usPathDataSize ); - if (pSoldier->bActionPoints >= bAPCost) + if (pSoldier->bActionPoints >= bAPCost ) { // seems to have enough points... NewDest(pSoldier,sNewGridNo); diff --git a/TileEngine/Explosion Control.cpp b/TileEngine/Explosion Control.cpp index 768b88da..120c37c6 100644 --- a/TileEngine/Explosion Control.cpp +++ b/TileEngine/Explosion Control.cpp @@ -1402,6 +1402,12 @@ BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT16 sBombGridNo usHalfExplosionRadius = Explosive[Item[usItem].ubClassIndex].ubRadius / 2; if ( fFlashbang && !gbWorldSectorZ && !fInBuilding && (UINT16)uiDist > usHalfExplosionRadius ) { + // HEADROCK HAM 3.3: Flashbang at half distance causes up to 6 suppression points. Roughly equivalent of being + // "lightly" shot at. + if (gGameExternalOptions.usExplosionSuppressionEffect > 0) + { + pSoldier->ubSuppressionPoints += (PreRandom(6) * gGameExternalOptions.usExplosionSuppressionEffect) / 100; + } // then no effect return( FALSE ); } @@ -1426,6 +1432,16 @@ BOOLEAN DamageSoldierFromBlast( UINT8 ubPerson, UINT8 ubOwner, INT16 sBombGridNo ubSpecial = DetermineFlashbangEffect( pSoldier, ubDirection, fInBuilding); } + // HEADROCK HAM 3.3: Explosions cause suppression based on distance. + if (gGameExternalOptions.usExplosionSuppressionEffect > 0) + { + pSoldier->ubSuppressionPoints += ((__max(0,((Explosive[Item[usItem].ubClassIndex].ubRadius * 3) - uiDist)))* gGameExternalOptions.usExplosionSuppressionEffect) / 100; + if (fFlashbang && (gbWorldSectorZ || fInBuilding) && (UINT16)uiDist <= usHalfExplosionRadius) + { + pSoldier->ubSuppressionPoints += (15 * gGameExternalOptions.usExplosionSuppressionEffect) / 100; + } + } + pSoldier->EVENT_SoldierGotHit( usItem, sNewWoundAmt, sBreathAmt, ubDirection, (INT16)uiDist, ubOwner, ubSpecial, ANIM_CROUCH, sSubsequent, sBombGridNo ); pSoldier->ubMiscSoldierFlags |= SOLDIER_MISC_HURT_BY_EXPLOSION; diff --git a/Utils/Cinematics.cpp b/Utils/Cinematics.cpp index b0f2d0a9..eb015286 100644 --- a/Utils/Cinematics.cpp +++ b/Utils/Cinematics.cpp @@ -193,7 +193,6 @@ SMKFLIC *pSmack; SMKFLIC *SmkOpenFlic(CHAR8 *cFilename) { SMKFLIC *pSmack; - // Get an available flic slot from the list if(!(pSmack=SmkGetFreeFlic())) diff --git a/Utils/Cursors.cpp b/Utils/Cursors.cpp index 1c087b3f..c1c574de 100644 --- a/Utils/Cursors.cpp +++ b/Utils/Cursors.cpp @@ -1266,7 +1266,7 @@ void DrawMouseGraphics( ) // 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) ) + if ( gbCtHAutoFire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) ) gbCtHBurstCount = 2; else if ( gbCtHAutoFire ) gbCtHBurstCount = 0; @@ -1274,9 +1274,9 @@ void DrawMouseGraphics( ) // 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) ) + if (gbCtHBurstCount > 1 && !gbCtHAutoFire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 2) ) barY = gsCurMouseOffsetY-__min(55,gsCurMouseHeight)/2; - else if (gbCtHBurstCount && gbCtHAutoFire && (gGameExternalOptions.iNewCTHBars == 1 || gGameExternalOptions.iNewCTHBars == 3) ) + else if (gbCtHBurstCount && gbCtHAutoFire && (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) ) barY = gsCurMouseOffsetY-__min(55,gsCurMouseHeight)/2; else barY = gsCurMouseOffsetY-__min(35,gsCurMouseHeight)/2; @@ -1378,7 +1378,7 @@ void DrawMouseText( ) FindFontCenterCoordinates( 0, 0, gsCurMouseWidth, gsCurMouseHeight, pStr, TINYFONT1, &sX, &sY ); // 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) + if (gGameExternalOptions.ubNewCTHBars == 1 || gGameExternalOptions.ubNewCTHBars == 3) // New CTH Bar moves bullet count to new location. mprintf( 46, sY - 15 - GetFontHeight(TINYFONT1), pStr ); else @@ -1387,9 +1387,10 @@ void DrawMouseText( ) // HEADROCK HAM B2: Removed this. The bottom line will now display bodypart targetting, like Burst and // Single. - if ( gGameExternalOptions.iNewCTHBars != 1 && gGameExternalOptions.iNewCTHBars != 3 ) + if ( gGameExternalOptions.ubNewCTHBars != 1 && gGameExternalOptions.ubNewCTHBars != 3 ) { swprintf( pStr, L"%d", gsTotalBulletCount ); + FindFontCenterCoordinates( 0, 0, gsCurMouseWidth, gsCurMouseHeight, pStr, TINYFONT1, &sX, &sY ); mprintf( sX, sY + 7 + GetFontHeight(TINYFONT1), pStr ); } diff --git a/Utils/STIConvert.cpp b/Utils/STIConvert.cpp index d1e15e7a..b1455505 100644 --- a/Utils/STIConvert.cpp +++ b/Utils/STIConvert.cpp @@ -105,8 +105,6 @@ void ConvertRGBDistribution555To565( UINT16 * p16BPPData, UINT32 uiNumberOfPixel void WriteSTIFile( INT8 *pData, SGPPaletteEntry *pPalette, INT16 sWidth, INT16 sHeight, STR cOutputName, UINT32 fFlags, UINT32 uiAppDataSize ) { - - UINT32 uiOriginalSize; UINT8 * pOutputBuffer = NULL; UINT32 uiCompressedSize; diff --git a/Utils/Text.h b/Utils/Text.h index 75e61b54..c960ec1e 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -173,6 +173,12 @@ extern STR16 gzIMPColorChoosingText[]; extern STR16 sColorChoiceExplanationTexts[]; extern STR16 gzIMPDisabilityTraitText[]; //**** + +// HEADROCK HAM 3.6: New arrays for facility operation messages +extern STR16 gzFacilityErrorMessage[]; +extern STR16 gzFacilityAssignmentStrings[]; +extern STR16 gzFacilityRiskResultStrings[]; + enum { ANTIHACKERSTR_EXITGAME, diff --git a/Utils/Utilities.cpp b/Utils/Utilities.cpp index 818304aa..5f1c3e71 100644 --- a/Utils/Utilities.cpp +++ b/Utils/Utilities.cpp @@ -238,7 +238,9 @@ BOOLEAN WrapString( STR16 pStr, STR16 pStr2, UINT16 usWidth, INT32 uiFont ) } if ( fLineSplit ) - break; + // HEADROCK HAM 3.6: This is erroneous. At this point, the function should RETURN, otherwise it + // cuts another bit off pStr for no reason. + return (fLineSplit); uiNewLet--; curletter--; @@ -250,7 +252,9 @@ BOOLEAN WrapString( STR16 pStr, STR16 pStr2, UINT16 usWidth, INT32 uiFont ) swprintf( pStr2, L"-%s", &(pStr[uiHyphenLet]) ); pStr[uiHyphenLet] = (INT16)'/0'; fLineSplit = TRUE; //hyphen method - break; + // HEADROCK HAM 3.6: This is erroneous. At this point, the function should RETURN, otherwise it + // cuts another bit off pStr for no reason. + return (fLineSplit); } } diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 9d6270ec..64664bc0 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -359,6 +359,7 @@ STR16 sTimeStrings[] = // Assignment Strings: what assignment does the merc have right now? For example, are they on a squad, training, // administering medical aid (doctor) or training a town. All are abbreviated. 8 letters is the longest it can be. +// HEADROCK HAM 3.6: Added string for Mobile Militia Training ("M.Militia"), string for Facility Staffing ("Facility") STR16 pAssignmentStrings[] = { L"Squad 1", @@ -388,9 +389,12 @@ STR16 pAssignmentStrings[] = L"In Trans", // in transit - abbreviated form L"Repair", // repairing L"Practice", // training themselves - L"Militia", // training a town to revolt + L"Militia", // training a town to revolt + L"M.Militia", //training moving militia units L"Trainer", // training a teammate L"Student", // being trained by someone else + L"Staff", // operating a strategic facility + L"Rest", // Resting at a facility L"Dead", // dead L"Incap.", // abbreviation for incapacitated L"POW", // Prisoner of war - captured @@ -404,6 +408,7 @@ STR16 pMilitiaString[] = L"Militia", // the title of the militia box L"Unassigned", //the number of unassigned militia troops L"You can't redistribute militia while there are hostilities in the area!", + L"Some militia were not assigned to a sector. Would you like to disband them?", // HEADROCK HAM 3.6 }; @@ -411,6 +416,7 @@ STR16 pMilitiaButtonString[] = { L"Auto", // auto place the militia troops for the player L"Done", // done placing militia troops + L"Disband", // HEADROCK HAM 3.6: Disband militia }; STR16 pConditionStrings[] = @@ -633,6 +639,7 @@ STR16 pAssignMenuStrings[] = L"Vehicle", // the merc is in a vehicle L"Repair", // the merc is repairing items L"Train", // the merc is training + L"Facility", // the merc is using/staffing a facility L"Cancel", // cancel this menu }; @@ -686,10 +693,12 @@ STR16 pAttributeMenuStrings[] = L"Cancel", }; +// HEADROCK HAM 3.6: Splitting Garrison and Mobile training. STR16 pTrainingMenuStrings[] = { L"Practice", // train yourself L"Militia", // train the town + L"Mobile Militia", L"Trainer", // train your teammates L"Student", // be trained by an instructor L"Cancel", // cancel this menu @@ -1106,7 +1115,9 @@ CHAR16 gWeaponStatsDesc[][ 16 ] = L"Weight:", //12 L"Stun Damage:",//13 // HEADROCK: Added new strings for extended description ** REDUNDANT ** - L"AUTOPEN:", //14 + // HEADROCK HAM 3: Replaced #14 with string for Possible Attachments (BR's Tooltips Only) + // Obviously, THIS SHOULD BE DONE IN ALL LANGUAGES... + L"Attachments:", //14 L"AUTO/5:", //15 L"Amount:", //16 @@ -1145,7 +1156,7 @@ STR16 gzWeaponStatsFasthelp[ 29 ] = L"No Auto Mode!", L"APs to Bash", L"Autofire Penalty (Lower is better)", - L"Burst Penalty (Lower is better)", + L"Burst Penalty (Lower is better)" }; STR16 gzWeaponStatsFasthelpTactical[ 29 ] = @@ -1178,7 +1189,7 @@ STR16 gzWeaponStatsFasthelpTactical[ 29 ] = L"No Auto Mode!", L"APs to Bash", L"Autofire Penalty (Lower is better)", - L"Burst Penalty (Lower is better)", + L"Burst Penalty (Lower is better)" }; STR16 gzAmmoStatsFasthelp[ 20 ] = @@ -1195,7 +1206,7 @@ STR16 gzAmmoStatsFasthelp[ 20 ] = L"Damage Modifier", L"To-Hit Modifier", L"Autofire Penalty Modifier (Higher is better)", - L"Burst/Auto Penalty Modifier (Higher is better)", + L"Burst Penalty Modifier (Higher is better)", L"Reliability Modifier", L"Loudness Modifier (Lower is better)", L"", @@ -1731,11 +1742,12 @@ STR16 gsTimeStrings[] = // text for the various facilities in the sector +// HEADROCK HAM 3.4: Changed "Industry" into "Factory" STR16 sFacilitiesStrings[] = { L"None", L"Hospital", - L"Industry", + L"Factory", L"Prison", L"Military", L"Airport", @@ -1766,6 +1778,7 @@ STR16 pwTownInfoStrings[] = L"Level", // the training level of civilians in this town L"Civilian Training", // 10 // state of civilian training in town L"Militia", // the state of the trained civilians in the town + L"Mobile Training", // HEADROCK HAM 3.6: The stat of Mobile militia training in town }; // Mine strings @@ -2069,6 +2082,8 @@ STR16 pTransactionText[] = L"Purchased items from %s.", //is used for the Shop keeper interface. The dealers name will be appended to the end of the string. L"%s deposited money.", L"Sold Item(s) to the Locals", + L"Facility Use", // HEADROCK HAM 3.6 + L"Militia upkeep", // HEADROCK HAM 3.6 }; STR16 pTransactionAlternateText[] = @@ -2163,7 +2178,7 @@ STR16 pMapErrorString[] = //11-15 L"Movement orders? There's a battle going on!", L"You have been ambushed by bloodcats in sector %s!", - L"You have just entered what appears to be a bloodcat lair in sector I16!", + L"You have just entered what appears to be a bloodcat lair in sector %s!", // HEADROCK HAM 3.6: Added argument. L"", L"The SAM site in %s has been taken over.", //16-20 @@ -3646,6 +3661,10 @@ STR16 pMilitiaConfirmStrings[] = L"You cannot afford the $%d to train town militia here.", L"%s needs a loyalty of %d percent for you to be able to continue training militia.", L"You cannot train the militia in %s any further.", + L"You cannot afford the $%d to train mobile militia here.", // HEADROCK HAM 3.6: Mobile Militia + L"Continue training mobile militia in %s (%s %d)?", // HEADROCK HAM 3.6: Mobile Militia + L"Training mobile militia in %d sectors will cost $ %d. %s", // HEADROCK HAM 3.6: Mobile Militia + L"Training a squad of mobile militia will cost $", // HEADROCK HAM 3.6: Mobile Militia }; //Strings used in the popup box when withdrawing, or depositing money from the $ sign at the bottom of the single merc panel @@ -3700,6 +3719,7 @@ STR16 zOptionsToggleText[] = L"Low CPU usage", L"Enhanced Description Box", L"Forced Turn Mode", // add forced turn mode + L"Stat Progress Bars", // Show progress towards stat increase L"--Cheat Mode Options--", // TOPTION_CHEAT_MODE_OPTIONS_HEADER, L"Force Bobby Ray shipments", // force all pending Bobby Ray shipments L"-----------------", // TOPTION_CHEAT_MODE_OPTIONS_END @@ -3795,6 +3815,7 @@ STR16 zOptionsScreenHelpText[] = L"When ON, game will run with much lower CPU usage.", L"When ON, enhanced descriptions will be shown for items and weapons.", L"When ON and enemy present, Turn Base mode persists untill sector is free (|C|T|R|L+|S|H|I|F|T+|A|L|T+|T).", // add forced turn mode + L"When ON, shows character progress towards gaining levels.", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_HEADER", L"Force all pending Bobby Ray shipments", L"(text not rendered)TOPTION_CHEAT_MODE_OPTIONS_END", @@ -4425,7 +4446,8 @@ STR16 gzLateLocalizedString[] = //46-48 L"%s's gun barrel extender falls off!", - L"No more than %d militia trainers are permitted per sector.", + // HEADROCK HAM 3.5: Changed to reflect facility effect. + L"No more than %d militia trainers are permitted in this sector.", L"Are you sure?", //49-50 @@ -4461,9 +4483,10 @@ STR16 gzLateLocalizedString[] = L"%s fires %d more round than intended!", }; +// HEADROCK HAM 3.5: Added sector name STR16 gzCWStrings[] = { - L"Call reinforcements from adjacent sectors?", + L"Call reinforcements to %s from adjacent sectors?", }; // WANNE: Tooltips @@ -4542,7 +4565,34 @@ STR16 New113Message[] = STR16 New113HAMMessage[] = { + // 0 - 5 L"%s cowers in fear!", + L"%s is pinned down!", + L"%s fires more rounds than intended!", + L"You cannot train militia in this sector.", + L"Militia picks up %s.", + L"Cannot train militia with enemies present!", + // 6 - 10 + L"%s lacks sufficient Leadership score to train militia.", + L"No more than %d Mobile Militia trainers are permitted in this sector.", + L"No room in %s or around it for new Mobile Militia!", + L"You need to have %d Town Militia in each of %s's liberated sectors before you can train Mobile Militia here.", + L"Can't staff a facility while enemies are present!", + // 11 - 15 + L"%s lacks sufficient Wisdom to staff this facility.", + L"The %s is already fully-staffed.", + L"It will cost $%d per hour to staff this facility. Do you wish to continue?", + L"You have insufficient funds to pay for all Facility work today. $%d have been paid, but you still owe $%d. The locals are not pleased.", + L"You have insufficient funds to pay for all Facility work today. You owe $%d. The locals are not pleased.", + // 16 - 20 + L"You have an outstanding debt of $%d for Facility Operation, and no money to pay it off!", + L"You have an outstanding debt of $%d for Facility Operation. You can't assign this merc to facility duty until you have enough money to pay off the entire debt.", + L"You have an outstanding debt of $%d for Facility Operation. Would you like to pay it all back?", + L"N/A in this sector", + L"Daily Expenses", + // 21 - 25 + L"Insufficient funds to pay all enlisted militia! %d militia have disbanded and returned home.", + }; // WANNE: This are the email texts, when one of the 4 new 1.13 MERC mercs have levelled up, that Speck sends @@ -4853,6 +4903,111 @@ STR16 gzIMPDisabilityTraitText[]= L"I.M.P. Disabilities", }; +// HEADROCK HAM 3.6: Error strings for assigning a merc to a facility +STR16 gzFacilityErrorMessage[]= +{ + L"%s lacks sufficient Strength to perform this task.", + L"%s lacks sufficient Dexterity to perform this task.", + L"%s lacks sufficient Agility to perform this task.", + L"%s is not Healthy enough to perform this task.", + L"%s lacks sufficient Wisdom to perform this task.", + L"%s lacks sufficient Marksmanship to perform this task.", + // 6 - 10 + L"%s lacks sufficient Medical Skill to perform this task.", + L"%s lacks sufficient Mechanical Skill to perform this task.", + L"%s lacks sufficient Leadership to perform this task.", + L"%s lacks sufficient Explosives Skill to perform this task.", + L"%s lacks sufficient Experience to perform this task.", + // 11 - 15 + L"%s lacks sufficient Morale to perform this task.", + L"%s is too exhausted to perform this task.", + L"Insufficient loyalty in %s. The locals refuse to allow you to perform this task.", + L"Too many people are already working at the %s.", + L"Too many people are already performing this task at the %s.", + // 16 - 20 + L"%s can find no items to repair.", + L"%s has lost some %s while working in sector %s!", + L"%s has lost some %s while working at the %s in %s!", + L"%s was injured while working in sector %s, and requires immediate medical attention!", + L"%s was injured while working at the %s in %s, and requires immediate medical attention!", + // 21 - 25 + L"%s was injured while working in sector %s. It doesn't seem too bad though.", + L"%s was injured while working at the %s in %s. It doesn't seem too bad though.", + L"The residents of %s seem upset about %s's presence.", + L"The residents of %s seem upset about %s's work at the %s.", + L"%s's actions in sector %s have caused loyalty loss throughout the region!", + // 26 - 30 + L"%s's actions at the %s in %s have caused loyalty loss throughout the region!", + L"%s is drunk.", // <--- This is a log message string. + L"%s has become severely ill in sector %s, and has been taken off duty.", + L"%s has become severely ill and cannot continue his work at the %s in %s.", + L"%s was injured in sector %s.", // <--- This is a log message string. + // 31 - 35 + L"%s was severely injured in sector %s.", //<--- This is a log message string. + + +}; + +STR16 gzFacilityRiskResultStrings[]= +{ + L"Strength", + L"Agility", + L"Dexterity", + L"Wisdom", + L"Health", + L"Marksmanship", + // 5-10 + L"Leadership", + L"Mechanical skill", + L"Medical skill", + L"Explosives skill", +}; + +STR16 gzFacilityAssignmentStrings[]= +{ + L"AMBIENT", + L"Staff", + L"Rest", + L"Repair Items", + L"Repair %s", // Vehicle name inserted here + L"Repair Robot", + // 6-10 + L"Doctor", + L"Patient", + L"Practice Strength", + L"Practice Dexterity", + L"Practice Agility", + L"Practice Health", + // 11-15 + L"Practice Marksmanship", + L"Practice Medical", + L"Practice Mechanical", + L"Practice Leadership", + L"Practice Explosives", + // 16-20 + L"Student Strength", + L"Student Dexterity", + L"Student Agility", + L"Student Health", + L"Student Marksmanship", + // 21-25 + L"Student Medical", + L"Student Mechanical", + L"Student Leadership", + L"Student Explosives", + L"Trainer Strength", + // 26-30 + L"Trainer Dexterity", + L"Trainer Agility", + L"Trainer Health", + L"Trainer Marksmanship", + L"Trainer Medical", + // 30-35 + L"Trainer Mechanical", + L"Trainer Leadership", + L"Trainer Explosives", +}; + STR16 Additional113Text[]= { L"Jagged Alliance 2 v1.13 windowed mode requires a color depth of 16bpp or less.", diff --git a/Utils/message.cpp b/Utils/message.cpp index 921dab7f..33027d15 100644 --- a/Utils/message.cpp +++ b/Utils/message.cpp @@ -45,8 +45,9 @@ typedef struct #define MAX_LINE_COUNT 6 #define X_START 2 #define MAX_AGE 10000 -#define LINE_WIDTH 320 -#define MAP_LINE_WIDTH 300 +// HEADROCK HAM 3.6: Turned into a variable. +UINT16 LINE_WIDTH; +UINT16 MAP_LINE_WIDTH; #define WIDTH_BETWEEN_NEW_STRINGS 5 #define BETAVERSION_COLOR FONT_ORANGE @@ -70,7 +71,12 @@ UINT8 gubCurrentMapMessageString = 0; BOOLEAN fOkToBeepNewMessage = TRUE; -static ScrollStringStPtr gpDisplayList[ MAX_LINE_COUNT ]; +// HEADROCK HAM 3.6: Increased size of absolute MAXIMUM possible displayed messages to 36 (the most that can be seen +// in a 1024x768 display. There's now an INI setting that allows displaying up to 36 in the tactical screen. +// Strategic screen not yet changed. Please note that there are also fail-safes that make sure that the actual +// maximum isn't more that can be displayed given the resolution being used. +//static ScrollStringStPtr gpDisplayList[ MAX_LINE_COUNT ]; +static ScrollStringStPtr gpDisplayList[ 36 ]; static ScrollStringStPtr gMapScreenMessageList[ 256 ]; extern ScrollStringStPtr pStringS=NULL; @@ -134,7 +140,8 @@ void PlayNewMessageSound( void ); void HandleLastQuotePopUpTimer( void ); - +// HEADROCK HAM 3.6: External boolean tells game to re-render map screen message log panel. +extern BOOLEAN fMapScreenBottomDirty; // functions @@ -335,8 +342,10 @@ void ClearDisplayedListOfTacticalStrings( void ) { // this function will go through list of display strings and clear them all out UINT32 cnt; - - for ( cnt = 0; cnt < MAX_LINE_COUNT; cnt++ ) + + // HEADROCK HAM 3.6: Number of messages to display now depends on external options. + //for ( cnt = 0; cnt < MAX_LINE_COUNT; cnt++ ) + for ( cnt = 0; cnt < gGameExternalOptions.ubMaxMessagesTactical; cnt++ ) { if ( gpDisplayList[ cnt ] != NULL ) { @@ -368,7 +377,6 @@ void ScrollString( ) INT16 iMsgYStart = ((UsingNewInventorySystem() == false)) ? SCREEN_HEIGHT - 150 : SCREEN_HEIGHT - 210; - // UPDATE TIMER suiTimer=GetJA2Clock(); @@ -380,6 +388,9 @@ void ScrollString( ) return; } + // HEADROCK HAM 3.6: This replaces the static variable MAX_LINE_COUNT for all tactical displays. + UINT8 ubLines = gGameExternalOptions.ubMaxMessagesTactical; + // DONOT UPDATE IF WE ARE SCROLLING! if ( gfScrollPending || gfScrollInertia ) { @@ -395,7 +406,7 @@ void ScrollString( ) iNumberOfMessagesOnQueue = GetMessageQueueSize( ); iMaxAge = MAX_AGE; - if( ( iNumberOfMessagesOnQueue > 0 )&&( gpDisplayList[ MAX_LINE_COUNT - 1 ] != NULL) ) + if( ( iNumberOfMessagesOnQueue > 0 )&&( gpDisplayList[ ubLines - 1 ] != NULL) ) { fDitchLastMessage = TRUE; } @@ -416,11 +427,11 @@ void ScrollString( ) } //AGE - for ( cnt = 0; cnt < MAX_LINE_COUNT; cnt++ ) + for ( cnt = 0; cnt < ubLines; cnt++ ) { if ( gpDisplayList[ cnt ] != NULL ) { - if( ( fDitchLastMessage ) && ( cnt == MAX_LINE_COUNT - 1 ) ) + if( ( fDitchLastMessage ) && ( cnt == ubLines - 1 ) ) { gpDisplayList[ cnt ]->uiTimeOfLastUpdate = iMaxAge; } @@ -446,12 +457,12 @@ void ScrollString( ) { // CHECK IF WE HAVE A SLOT! // CHECK OUR LAST SLOT! - if ( gpDisplayList[ MAX_LINE_COUNT - 1 ] == NULL ) + if ( gpDisplayList[ ubLines - 1 ] == NULL ) { // MOVE ALL UP! // cpy, then move - for( cnt = MAX_LINE_COUNT - 1; cnt > 0; cnt-- ) + for( cnt = ubLines - 1; cnt > 0; cnt-- ) { gpDisplayList[ cnt ] = gpDisplayList[ cnt - 1 ]; } @@ -469,7 +480,7 @@ void ScrollString( ) pStringS->uiTimeOfLastUpdate = GetJA2Clock(); // now move - for ( cnt = 0; cnt <= MAX_LINE_COUNT - 1; cnt++ ) + for ( cnt = 0; cnt <= (UINT8)(ubLines - 1); cnt++ ) { // Adjust position! @@ -500,7 +511,7 @@ void ScrollString( ) } //check if new meesage we have not seen since mapscreen..if so, beep - if( ( fOkToBeepNewMessage == TRUE ) && ( gpDisplayList[ MAX_LINE_COUNT - 2 ] == NULL ) && ( ( guiCurrentScreen == GAME_SCREEN ) || ( guiCurrentScreen == MAP_SCREEN ) ) && ( gfFacePanelActive == FALSE ) ) + if( ( fOkToBeepNewMessage == TRUE ) && ( gpDisplayList[ ubLines - 2 ] == NULL ) && ( ( guiCurrentScreen == GAME_SCREEN ) || ( guiCurrentScreen == MAP_SCREEN ) ) && ( gfFacePanelActive == FALSE ) ) { PlayNewMessageSound( ); } @@ -541,7 +552,19 @@ void HideMessagesDuringNPCDialogue( void ) fScrollMessagesHidden = TRUE; uiStartOfPauseTime = GetJA2Clock(); - for ( cnt = 0; cnt < MAX_LINE_COUNT; cnt++ ) + UINT8 ubLines = 0; + + // HEADROCK HAM 3.6: Different number of lines shown in Tactical and Strategic modes. + if( guiCurrentScreen == MAP_SCREEN ) + { + ubLines = MAX_LINE_COUNT; + } + else + { + ubLines = gGameExternalOptions.ubMaxMessagesTactical; + } + + for ( cnt = 0; cnt < ubLines; cnt++ ) { if ( gpDisplayList[ cnt ] != NULL ) { @@ -567,7 +590,19 @@ void UnHideMessagesDuringNPCDialogue( void ) VideoOverlayDesc.uiFlags = VOVERLAY_DESC_DISABLED; fScrollMessagesHidden = FALSE; - for ( cnt = 0; cnt < MAX_LINE_COUNT; cnt++ ) + UINT8 ubLines = 0; + + // HEADROCK HAM 3.6: Different number of lines shown in Tactical and Strategic modes. + if( guiCurrentScreen == MAP_SCREEN ) + { + ubLines = MAX_LINE_COUNT; + } + else + { + ubLines = gGameExternalOptions.ubMaxMessagesTactical; + } + + for ( cnt = 0; cnt < ubLines; cnt++ ) { if ( gpDisplayList[ cnt ] != NULL ) { @@ -804,10 +839,12 @@ void TacticalScreenMsg( UINT16 usColor, UINT8 ubPriority, STR16 pStringA, ... ) if ( ubPriority == MSG_INTERFACE ) { - usColor = INTERFACE_COLOR; + // HEADROCK HAM 3.6: Why force yellow? Let's not. + //usColor = INTERFACE_COLOR; } - + // HEADROCK HAM 3.6: Allow for longer lines. + LINE_WIDTH = (SCREEN_WIDTH - 320); pStringWrapperHead=LineWrap(uiFont, LINE_WIDTH, &usLineWidthIfWordIsWiderThenWidth, DestString); pStringWrapper=pStringWrapperHead; @@ -1004,9 +1041,13 @@ void MapScreenMessage( UINT16 usColor, UINT8 ubPriority, STR16 pStringA, ... ) if ( ubPriority == MSG_INTERFACE ) { - usColor = INTERFACE_COLOR; + // HEADROCK HAM 3.6: Why force yellow? Let's not. + //usColor = INTERFACE_COLOR; } + // HEADROCK HAM 3.6: Allow for longer lines. + MAP_LINE_WIDTH = (SCREEN_WIDTH - 330); + pStringWrapperHead=LineWrap(uiFont, MAP_LINE_WIDTH, &usLineWidthIfWordIsWiderThenWidth, DestString); pStringWrapper=pStringWrapperHead; if(!pStringWrapper) @@ -1023,7 +1064,9 @@ void MapScreenMessage( UINT16 usColor, UINT8 ubPriority, STR16 pStringA, ... ) } AddStringToMapScreenMessageList(pStringWrapper->sString, usColor, uiFont, fNewString, ubPriority ); - + // HEADROCK HAM 3.6: Refresh screen bottom. + fMapScreenBottomDirty = TRUE; + //RenderMapScreenInterfaceBottom ( TRUE ); // clear up list of wrapped strings ClearWrappedStrings( pStringWrapperHead ); @@ -1099,7 +1142,8 @@ void DisplayStringsInMapScreenMessageList( void ) // CHRISL: Change both X paramters so they dynamically generate from right edge of screen //SetFontDestBuffer( FRAME_BUFFER, (SCREEN_WIDTH - 509), (SCREEN_HEIGHT - 114), (SCREEN_WIDTH - 233), (SCREEN_HEIGHT - 114) + 101, FALSE ); // CHRISL: Use this setup if we want message box on the left side - SetFontDestBuffer( FRAME_BUFFER, 17, (SCREEN_HEIGHT - 114), 407, (SCREEN_HEIGHT - 114) + 101, FALSE ); + // HEADROCK HAM 3.6: Message window now as wide as possible. The money screen has been moved to the right side. + SetFontDestBuffer( FRAME_BUFFER, 17, (SCREEN_HEIGHT - 114), (SCREEN_WIDTH - 330), (SCREEN_HEIGHT - 114) + 101, FALSE ); SetFont( MAP_SCREEN_MESSAGE_FONT ); // no longer supports variable fonts SetFontBackground( FONT_BLACK ); @@ -1150,8 +1194,18 @@ void EnableDisableScrollStringVideoOverlay( BOOLEAN fEnable ) // will go through the list of video overlays for the tactical scroll message system, and enable/disable // video overlays depending on fEnable INT8 bCounter = 0; + UINT8 ubLines = 0; - for( bCounter = 0; bCounter < MAX_LINE_COUNT; bCounter++ ) + if( guiCurrentScreen == MAP_SCREEN ) + { + ubLines = MAX_LINE_COUNT; + } + else + { + ubLines = gGameExternalOptions.ubMaxMessagesTactical; + } + + for( bCounter = 0; bCounter < ubLines; bCounter++ ) { // if valid, enable/disable diff --git a/ja2_2005Express.vcproj b/ja2_2005Express.vcproj index e6aa327f..6990671d 100644 --- a/ja2_2005Express.vcproj +++ b/ja2_2005Express.vcproj @@ -17,7 +17,7 @@