Files
source/Strategic/MilitiaSquads.cpp
T
Wanne 923327ca14 Mobile Miltia improvements (by Moa)
- ja2_options.ini: MOBILE_MILITIA_MAX_ACTIVE_MODE, MOBILE_MILITIA_MAX_ACTIVE_MODIFIER
- see: http://www.bears-pit.com/board/ubbthreads.php/topics/325103/2.html


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@6544 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2013-10-30 19:48:45 +00:00

2616 lines
97 KiB
C++

#ifdef PRECOMPILEDHEADERS
#include "Strategic All.h"
#else
#include "Town Militia.h"
#include "Militia Control.h"
#include "Campaign Types.h"
#include "strategic.h"
#include "strategicmap.h"
#include "Overhead.h"
#include "Strategic Town Loyalty.h"
#include "Utilities.h"
#include "random.h"
#include "text.h"
#include "Map Screen Interface.h"
#include "Interface.h"
#include "Laptopsave.h"
#include "Game Clock.h"
#include "Assignments.h"
#include "squads.h"
#include "Soldier Create.h"
#include "Dialogue Control.h"
#include "GameSettings.h"
#include "Queen Command.h"
#include "math.h"
#include "Auto Resolve.h"
#include "Vehicles.h"
#include "Tactical Save.h"
#include "Campaign.h"
#include "message.h"
#endif
#include "connect.h"
#include "MilitiaSquads.h"
#include "Reinforcement.h"
#include "Inventory Choosing.h" // added by Flugente for MoveOneMilitiaEquipmentSet() and MoveMilitiaEquipment()
// Debug defines
//#define DEBUG_SHOW_RATINGS
#ifdef DEBUG_SHOW_RATINGS
#define DEBUG_RATINGS_CONDITION TRUE
//#define DEBUG_RATINGS_CONDITION !fForBattle
#include "message.h"
#endif
// will create a squad each n hours (12 for example). Should divide 24 without remainder
//#define CREATE_EACH_N_HOURS 12
// more these values - less the chances
#define CHANCE_TO_MOVE_A_SQUAD 5
#define CHANCE_TO_MOVE_AN_UNFULL_SQUAD 40
//#define DIV_OF_ORIGINAL_MILITIA 2
#define BASE_DIR_PRIORITY 5
// little chance to move if below minimum
//#define MINIMUM_MILITIA_SQUAD_SIZE 10
// should be 2* Minimum
//#define MAXIMUM_MILITIA_SQUAD_SIZE 30
//#define MAXIMUM_MILITIA_SQUAD_SIZE_BATTLE MAXIMUM_MILITIA_SQUAD_SIZE
#define DIR_WITH_UNFULL_SQUAD_RATING_BONUS 100
// rating of battle, when there's equal number of soldiers
//#define DIR_RATING_OF_BATTLE 10
#define DIR_MIN_DIF 0.75
#define DIR_NORTH 0
#define DIR_EAST 1
#define DIR_SOUTH 2
#define DIR_WEST 3
INT32 iRestrictedSectorArraySize;
UINT32 gRestrictMilitia[256];
// HEADROCK HAM 4: Yet ANOTHER array, this one holds player-set restrictions.
UINT8 gubManualRestrictMilitia[ 256 ];
// HEADROCK HAM B1: Alternate array keeps track of dynamically unrestricted sectors
BOOLEAN gDynamicRestrictMilitia[ 256 ];
// HEADROCK HAM B1: Function that dynamically unrestricts sectors as we take over towns.
// HEADROCK HAM 5: New flag tells us to also recheck restriced sectors.
extern void AdjustRoamingRestrictions( BOOLEAN fRecheck );
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;
INT16 sMSMapX, sMSMapY;
extern UINT8 GetTraversability( INT16 sStartSector, INT16 sEndSector );
extern void TriggerPrebattleInterface( UINT8 ubResult );
extern BOOLEAN gfEnterAutoResolveMode;
//Counts the number of live mercs in any given sector.
BOOLEAN PlayerMercsInSector_MSE( UINT8 ubSectorX, UINT8 ubSectorY, BOOLEAN fDontConsiderNextSector )
{
GROUP *pGroup;
PLAYERGROUP *pPlayer;
UINT8 ubNumMercs = 0;
pGroup = gpGroupList;
while( pGroup )
{
if( pGroup->fPlayer )// && !pGroup->fBetweenSectors)
{
if ( ( pGroup->ubSectorX == ubSectorX && pGroup->ubSectorY == ubSectorY && pGroup->ubSectorZ == 0 ) ||
( !fDontConsiderNextSector && ( pGroup->ubNextX == ubSectorX && pGroup->ubNextY == ubSectorY && pGroup->ubSectorZ == 0 ) ) )
{
//we have a group, make sure that it isn't a group containing only dead members.
pPlayer = pGroup->pPlayerList;
while( pPlayer )
{
// robots count as mercs here, because they can fight, but vehicles don't
if( ( pPlayer->pSoldier->stats.bLife ) && !( pPlayer->pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) )
{
ubNumMercs++;
}
pPlayer = pPlayer->next;
}
}
}
pGroup = pGroup->next;
}
return ubNumMercs;
}
// get any enemy group id in the sector (if no group, will return 0 )
UINT8 GetEnemyGroupIdInSector( INT16 sMapX, INT16 sMapY )
{
UINT8 ubRes = 0;
GROUP *curr;
curr = gpGroupList;
while( curr )
{
if( curr->ubSectorX == sMapX && curr->ubSectorY == sMapY && !curr->fPlayer )
if( !curr->ubGroupID )
return curr->ubGroupID;
else
ubRes = curr->ubGroupID;
curr = curr->next;
}
return ubRes;
}
// Count all militia in the sector
UINT8 CountMilitia(SECTORINFO *pSectorInfo)
{
return pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA]+
pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA]+
pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
}
// Creates militia at destination sector. The type and amount of militia depends on current sector's miltia type and amount
// HEADROCK HAM 3.4: Added Leadership argument.
void GenerateMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY, UINT8 ubBestLeadership )
{
//////////////////////////////////////////////////////////////////////////////////////
// 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 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.guiNumMobileMilitiaTrained;
// Percentage of troops that should be of any one type:
UINT8 ubTargetElitePercent = 0;
UINT8 ubTargetRegularPercent = 0;
UINT8 ubTempLeadership = ubBestLeadership;
UINT8 ubActualyAdded = 0; // Added by SANDRO
// Does trainer have enough leadership to train a squad?
if ( ubBestLeadership < gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia )
{
// 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
{
// HEADROCK HAM 3.6: New function to do Quantity Calculation based on leadership
ubMilitiaToTrain = CalcNumMilitiaTrained(ubBestLeadership, TRUE);
}
// 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.
// HEADROCK HAM 3.5: Base percentages off the trainer's Leadership...
if ( gGameExternalOptions.fLeadershipAffectsMobileMilitiaQuality &&
(ubBestLeadership >= gGameExternalOptions.ubMinimumLeadershipToTrainMobileMilitia) )
{
// 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 = 0;
if (bRange > 0)
ubBestLeadershipPercentage = (ubBestLeadership * 100) / bRange;
else
bRange = 0;
// 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
{
ubTargetElitePercent = 0; // No elites
ubTargetRegularPercent = 0; // No regulars
// All green.
}
else if (ubBestLeadershipPercentage >= 20 && ubBestLeadershipPercentage < 40)
{
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 (ubBestLeadershipPercentage >= 40 && ubBestLeadershipPercentage < 60)
{
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--;
ubActualyAdded++;
MoveOneMilitiaEquipmentSet(sMapX, sMapY, sTMapX, sTMapY, ELITE_MILITIA);
}
else if (ubTargetRegular > 0)
{
// Add regular.
StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, 1);
ubTargetRegular--;
ubMilitiaToTrain--;
ubActualyAdded++;
MoveOneMilitiaEquipmentSet(sMapX, sMapY, sTMapX, sTMapY, REGULAR_MILITIA);
}
else if (ubTargetGreen > 0)
{
// Add green.
StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, 1);
ubTargetGreen--;
ubMilitiaToTrain--;
ubActualyAdded++;
MoveOneMilitiaEquipmentSet(sMapX, sMapY, sTMapX, sTMapY, GREEN_MILITIA);
}
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--;
ubActualyAdded++;
}
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, REGULAR_MILITIA, 1);
ubTargetElite--;
ubMilitiaToTrain--;
ubActualyAdded++;
}
// 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--;
ubActualyAdded++;
}
else
{
// No regulars to train? Remove two greens instead.
ubTargetGreen -= 2;
ubMilitiaToTrain -= 2;
ubActualyAdded += 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--;
ubActualyAdded++;
}
else
{
// No more men to train. Break the cycle.
ubMilitiaToTrain = 0;
}
}
}
else
{
// Sector is full-sized and full-quality. Break the cycle.
ubMilitiaToTrain = 0;
}
}
}
// SANDRO - merc records (num militia trained)
if( ubActualyAdded > 0 )
RecordNumMilitiaTrainedForMercs( sMapX, sMapY, 0, ubActualyAdded, TRUE );
// 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];
}
}
// Update the militia if the current sector is affected
if (gfStrategicMilitiaChangesMade)
{
ResetMilitia();
}
}
// Creates militia at destination sector and removes it from starting sector
void MoveMilitiaSquad(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY, BOOLEAN fAlternativeMax )
{
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
SECTORINFO *pTSectorInfo = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] );
UINT8 bGreensSourceTeam = 0, bGreensDestTeam = 0;
UINT8 bRegularsSourceTeam = 0, bRegularsDestTeam = 0;
UINT8 bElitesSourceTeam = 0, bElitesDestTeam = 0;
UINT8 bTotalGreens = 0, bTotalRegulars = 0, bTotalElites = 0;
UINT8 bTotalGreensPercent = 0, bTotalRegularsPercent = 0, bTotalElitesPercent = 0;
INT8 bNewSourceGroupSize = 0, bNewDestGroupSize = 0, bGroupSizeRatio = 0;
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.
// 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.
UINT8 ubNumEnemiesNearOrigin = NumEnemiesInFiveSectors( sMapX, sMapY );
UINT8 ubNumEnemiesNearTarget = NumEnemiesInFiveSectors( sTMapX, sTMapY );
if (ubNumEnemiesNearOrigin == 0 &&
ubNumEnemiesNearTarget > 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 (ubNumEnemiesNearOrigin > 0 &&
ubNumEnemiesNearTarget == 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 if (ubNumEnemiesNearOrigin > 0 &&
ubNumEnemiesNearTarget > 0 )
{
// Both sectors threatened.
//Moa: removed below: reinforcement is handled elsewhere, the function below is even better then 50%/50% because it leaves
//a chance to make one sector fully defended instead of possibly two losses. Talking about unfull groups - full groups wont change anyway.
/*if (gGameExternalOptions.gfAllowReinforcements && !gGameExternalOptions.gfAllowReinforcementsOnlyInCity)
{
// Militia will spread out between the two sectors to maximize defensive capability, since they can
// always reinforce one another if either sector is attacked.
ubChanceToSpreadOut = 100;
}
else*/
{
// Militia chance to spread out is based on whether the Origin or the Destination are under greater
// threat. If the destination is more threatened, militia are more likely to reinforce it
// (move en-masse). If the origin is more threatened, militia are more likely to spread out evenly
// between the two sectors, to give at least some chance of defeating ONE of the enemy groups.
UINT16 usTotalNumEnemies = ubNumEnemiesNearTarget + ubNumEnemiesNearOrigin;
ubChanceToSpreadOut = __min( 100, (ubNumEnemiesNearOrigin * 100) / usTotalNumEnemies );
}
}
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;
}
// HEADROCK HAM 4: Groups moving into a NO_LEAVE sector will always reinforce it.
// this ignores any of the possible options above.
if (gubManualRestrictMilitia[ SECTOR(sTMapX, sTMapY) ] == MANUAL_MOBILE_NO_LEAVE )
{
ubChanceToSpreadOut = 0;
}
if (!gGameExternalOptions.gfAllowMilitiaSpread )
{
// Spreading is not allowed by user. Let them fill up the target sector.
ubChanceToSpreadOut = 0;
}
else if ( !gGameExternalOptions.gfAllowMilitiaSpreadWhenFollowing && PlayerMercsInSector_MSE( (UINT8) sTMapX, (UINT8) sTMapY, FALSE ) )
{
// There is a Player in targetsector or about to arrive, but spreading is not allowed by user.
// Let them fill up the target sector
ubChanceToSpreadOut = 0;
}
}
}
// Should we spread out into the target sector, or move en-masse?
if (PreRandom(100) < ubChanceToSpreadOut)
{
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 ];
bRegularsDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ];
bElitesSourceTeam = pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ];
bElitesDestTeam = pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ];
bTotalGreens = bGreensSourceTeam + bGreensDestTeam;
bTotalRegulars = bRegularsSourceTeam + bRegularsDestTeam;
bTotalElites = bElitesSourceTeam + bElitesDestTeam;
// Source team gets half of each group, rounded down
bGreensSourceTeam = bTotalGreens / 2;
bRegularsSourceTeam = bTotalRegulars / 2;
bElitesSourceTeam = bTotalElites / 2;
// Destination team gets half of each group, rounded up
bGreensDestTeam = bTotalGreens - bGreensSourceTeam;
bRegularsDestTeam = bTotalRegulars - bRegularsSourceTeam;
bElitesDestTeam = bTotalElites - bElitesSourceTeam;
// Flugente: mobiles take along their gear
// move only gear for those who come new into a sector
UINT8 elites = max(0, bElitesDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]);
UINT8 regulars = max(0, bRegularsDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]);
UINT8 greens = max(0, bGreensDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]);
MoveMilitiaEquipment(sMapX, sMapY, sTMapX, sTMapY, elites, regulars, greens);
// 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
{
// 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;
// Flugente: mobiles take along their gear
// move only gear for those who come new into a sector
UINT8 elites = max(0, bElitesDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]);
UINT8 regulars = max(0, bRegularsDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]);
UINT8 greens = max(0, bGreensDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]);
MoveMilitiaEquipment(sMapX, sMapY, sTMapX, sTMapY, elites, regulars, greens);
// Add half team to target sector
StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, bGreensDestTeam );
StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, bRegularsDestTeam );
StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, bElitesDestTeam );
// 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
{
// 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 )
{
// 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 ];
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)
{
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;
}
}
// Flugente: mobiles take along their gear
// move only gear for those who come new into a sector
UINT8 elites = max(0, bElitesDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ]);
UINT8 regulars = max(0, bRegularsDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ]);
UINT8 greens = max(0, bGreensDestTeam - pTSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ]);
MoveMilitiaEquipment(sMapX, sMapY, sTMapX, sTMapY, elites, regulars, greens);
// 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
{
// Flugente: mobiles take along their gear
MoveMilitiaEquipment(sMapX, sMapY, sTMapX, sTMapY, pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ], pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ], pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_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 ] );
// Block target group from moving. It has just assimilated another group.
AddToBlockMoveList( sTMapX, sTMapY );
}
}
while( !fAlternativeMax && CountMilitia(pTSectorInfo) > gGameExternalOptions.iMaxMilitiaPerSector )
{
if(pTSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA])
{
--pTSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA];
++pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA];
}
else if(pTSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA])
{
--pTSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA];
++pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA];
}
else if(pTSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA])
{
--pTSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
++pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
}
}
if (gfStrategicMilitiaChangesMade)
{
ResetMilitia();
}
}
BOOLEAN MoveOneBestMilitiaMan(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMapY)
{
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
// SECTORINFO *pTSectorInfo = &( SectorInfo[ SECTOR( sTMapX, sTMapY ) ] );
if( pSectorInfo->ubNumberOfCivsAtLevel[ ELITE_MILITIA ] )
{
StrategicAddMilitiaToSector( sTMapX, sTMapY, ELITE_MILITIA, 1 );
StrategicRemoveMilitiaFromSector( sMapX, sMapY, ELITE_MILITIA, 1 );
return TRUE;
}
if( pSectorInfo->ubNumberOfCivsAtLevel[ REGULAR_MILITIA ] )
{
StrategicAddMilitiaToSector( sTMapX, sTMapY, REGULAR_MILITIA, 1 );
StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, 1 );
return TRUE;
}
if( pSectorInfo->ubNumberOfCivsAtLevel[ GREEN_MILITIA ] )
{
StrategicAddMilitiaToSector( sTMapX, sTMapY, GREEN_MILITIA, 1 );
StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, 1 );
return TRUE;
}
return FALSE;
}
// Generate rating for a direction
UINT16 CountDirectionEnemyRating( INT16 sMapX, INT16 sMapY, UINT8 uiDir )
{
DOUBLE ddMinAngle, ddMaxAngle;
DOUBLE ddRes = 0;
DOUBLE pix2 = 3.1415926 * 2; // pi * 2
//UINT16 uiSector = 0;
INT16 sLMX, sLMY;
switch(uiDir)
{
case DIR_NORTH:
ddMinAngle = -2.3561944; // -135 degrees
ddMaxAngle = -0.7853981; // -45 degrees
break;
case DIR_EAST:
ddMinAngle = -0.7853981; // -45 degrees
ddMaxAngle = 0.7853981; // 45 degrees
break;
case DIR_SOUTH:
ddMinAngle = 0.7853981; // 45 degrees
ddMaxAngle = 2.3561944; // 135 degrees
break;
case DIR_WEST:
ddMinAngle = 2.3561944; // 135 degrees
ddMaxAngle = -2.3561944 + pix2; // 225 degrees(-135)
break;
default:
return 0;
}
//for( uiSector = 0 ; uiSector < 256 ; ++uiSector )
for( sLMX = MINIMUM_VALID_X_COORDINATE; sLMX <= MAXIMUM_VALID_X_COORDINATE ; ++sLMX )
for( sLMY = MINIMUM_VALID_Y_COORDINATE; sLMY <= MAXIMUM_VALID_Y_COORDINATE ; ++sLMY )
{
// SECTORINFO *pSectorInfo = &( SectorInfo[ uiSector ] );
UINT8 uiSumOfEnemyTroops = NumEnemiesInSector( sLMX, sLMY );
//pSectorInfo->ubNumAdmins + pSectorInfo->ubNumTroops + pSectorInfo->ubNumElites;
// there's an enemy
if( uiSumOfEnemyTroops )
{
DOUBLE ddAngle;
if( sLMX - sMapX == 0 )ddAngle = ( sLMY - sMapY > 0 ) ? 1.57079632 : -1.57079632;
else ddAngle = atan2( (double)(sLMY - sMapY), (double)(sLMX - sMapX) );
if( ddAngle < 0 && uiDir == DIR_WEST )ddAngle += pix2;
// is it in a right direction?
if( ddAngle >= ddMinAngle && ddAngle <= ddMaxAngle )
{
DOUBLE ddDistance = sqrt( pow( (double)(sLMY - sMapY), 2 ) + pow( (double)(sLMX - sMapX), 2 ) );
ddRes += (DOUBLE)uiSumOfEnemyTroops / pow( ddDistance, 2 );
}
}
}
return (UINT16) ddRes;
}
UINT16 CountDirectionRating( INT16 sMapX, INT16 sMapY, UINT8 uiDir )
{
INT32 iRes = BASE_DIR_PRIORITY + CountDirectionEnemyRating( sMapX, sMapY, uiDir );
INT16 sDMapX, sDMapY;
FLOAT iDiff;
switch(uiDir)
{
case DIR_NORTH:
sDMapX = sMapX;
sDMapY = sMapY - 1;
break;
case DIR_EAST:
sDMapX = sMapX + 1;
sDMapY = sMapY;
break;
case DIR_SOUTH:
sDMapX = sMapX;
sDMapY = sMapY + 1;
break;
case DIR_WEST:
sDMapX = sMapX - 1;
sDMapY = sMapY;
break;
default:
return iRes;
}
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;
// Modify desire for militia-initiated attack
if( ubTargetEnemies )
{
iDiff = (FLOAT)(usTargetMilitiaInFiveSectors / usTargetEnemiesInFiveSectors);
if( iDiff > (FLOAT)DIR_MIN_DIF )
iRes = (INT32)((FLOAT)iRes * iDiff);
else
iRes = 0;
}
// HEADROCK HAM 4: Increase desire to move into a NO-LEAVE sector (Player-Set restrictions system)
if (gubManualRestrictMilitia[ SECTOR(sDMapX, sDMapY) ] == MANUAL_MOBILE_NO_LEAVE )
{
iRes = (INT32)((FLOAT)iRes * 1.5);
}
// 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 )) );
return iRes;
}
// Standard conditions the direction should fit
/////////////////////////////////////////////////
// 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 )
{
SECTORINFO * pTargetSector = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
if (fForBattle)
{
// 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
{
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 )
{
// Allow movement into restricted sectors ONLY if they are for battle!
fRoamingAllowed = TRUE;
}
else
{
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;
}
}
// 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 fForTraining, BOOLEAN fForBattle )
{
*uiDirNumber = 0;
#ifdef DEBUG_SHOW_RATINGS
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, fForTraining, fForBattle ) )
{
pMoveDir[ *uiDirNumber ][0] = SECTOR( sMapX, sMapY - 1 );
if( fForBattle )
pMoveDir[ *uiDirNumber ][1] = 0;
else
pMoveDir[ *uiDirNumber ][1] = CountDirectionRating( sMapX, sMapY, DIR_NORTH );
pMoveDir[ *uiDirNumber ][2] = INSERTION_CODE_NORTH;
#ifdef DEBUG_SHOW_RATINGS
if( DEBUG_RATINGS_CONDITION )ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"Priority of north direction - %ld", pMoveDir[ *uiDirNumber ][1]);
#endif
++(*uiDirNumber);
}
if(sMapX > MINIMUM_VALID_X_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX - 1, sMapY, fForTraining, fForBattle ))
{
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);
}
if(sMapY < MAXIMUM_VALID_Y_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX, sMapY + 1, fForTraining, fForBattle ))
{
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);
}
if(sMapX < MAXIMUM_VALID_X_COORDINATE && CheckStandardConditionsForDirection( sMapX, sMapY, sMapX + 1, sMapY, fForTraining, fForBattle ))
{
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);
}
}
UINT8 pBlockMoveList[256];
UINT8 uiBlockMoveListPointer;
void AddToBlockMoveList(INT16 sMapX, INT16 sMapY)
{
pBlockMoveList[ uiBlockMoveListPointer++ ] = SECTOR( sMapX, sMapY );
}
void ClearBlockMoveList()
{
uiBlockMoveListPointer = 0;
}
BOOLEAN CheckInBlockMoveList(INT16 sMapX, INT16 sMapY)
{
UINT16 i;
for( i = 0; i < uiBlockMoveListPointer ; ++i)
if( sMapX == SECTORX(pBlockMoveList[i]) && sMapY == SECTORY(pBlockMoveList[i]) )return TRUE;
return FALSE;
}
void UpdateMilitiaSquads(INT16 sMapX, INT16 sMapY )
{
UINT16 pMoveDir[4][3];
UINT8 uiDirNumber = 0;
UINT32 iRandomRes = 0, iRandom = 0;
UINT8 x;//,y;
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
UINT8 uiMilitiaCount;
BOOLEAN fSourceCityAllowsRoaming;
// HEADROCK HAM 4: Player can set a sector to NO_LEAVE, forcing Mobiles to stay put if they enter the sector at all.
if (gubManualRestrictMilitia[ SECTOR( sMapX, sMapY ) ] == MANUAL_MOBILE_NO_LEAVE )
{
// NO_LEAVE zone. Militia can't even consider moving.
return;
}
// If we don't want roaming militia
// We shouldn't be here.
if( !gGameExternalOptions.gfAllowMilitiaGroups )
return;
if( sMapX == 1 && sMapY == 1 )
{
ClearBlockMoveList();
gfMSBattle = FALSE;
}
if( CheckInBlockMoveList( sMapX, sMapY ) )return;
uiMilitiaCount = CountMilitia(pSectorInfo);
if( !uiMilitiaCount )return;
// Kaiden: Moved Create Militia code into CreateMilitiaSquads
// Removed the code from here and added the procedure call
// If it's time, and this is a city sector create a new group.
// But only if we're not training our own.
if( (!gGameExternalOptions.gfmusttrainroaming) && ( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ) )
{
if (GetWorldDay( ) < gGameExternalOptions.guiAllowMilitiaGroupsDelay)
return;
if( GetWorldHour() % gGameExternalOptions.guiCreateEachNHours )
return;
CreateMilitiaSquads( sMapX, sMapY );
if (gfStrategicMilitiaChangesMade)
{
ResetMilitia();
}
}
// HEADROCK HAM B2.7: If INI flag is set, allow militia to move out of Minor City sectors (Orta, Tixa, San Mona, etc).
if (gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities)
fSourceCityAllowsRoaming = ( !gfMilitiaAllowedInTown[GetTownIdForSector( sMapX, sMapY )] );
else
fSourceCityAllowsRoaming = ( GetTownIdForSector( sMapX, sMapY ) == BLANK_SECTOR );
// moving squad, if it is not a SAM site
if( ( fSourceCityAllowsRoaming ) && (!IsThisSectorASAMSector( sMapX, sMapY, 0 )) )
{
if( !gGameExternalOptions.gfAllowMilitiaFollowPlayer || !PlayerMercsInSector_MSE( (UINT8)sMapX, (UINT8)sMapY, FALSE ) ) // and there's no player's mercs in the sector, or they are not forced to follow
{
if( GetWorldHour() % 2 )return;
memset(pMoveDir, 0, sizeof(pMoveDir));
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]);
// 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);
iRandomRes = 256;
for( x = 0; x < uiDirNumber; ++x)
if( iRandom < pMoveDir[x][1] )
{
iRandomRes = x;
break;
}
// "Decided" to stay here
if(iRandomRes >= uiDirNumber)
return;
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%ld,%ld:Dir count %ld, Rand %ld. Go to %ld,%ld. Have %ld militia men", sMapX, sMapY, uiDirNumber, iRandomRes, SECTORX( pMoveDir[ iRandomRes ][0] ), SECTORY( pMoveDir[ iRandomRes ][0] ), uiMilitiaCount);
//Kaiden: if Restricted Sectors List option is turned on,
// militia can't move to any sectors in the list.
// Unless they are following a group of mercs.
///////////////////////////////////////////////////////////////////
// HEADROCK HAM B1: Restrict roamers by XML as normal, but also
// allow them to move through visited sectors.
// Also, there's an option now to use dynamic restriction. The
// AdjustRoamingRestrictions() function is used to dynamically
// define which sectors are restricted, based on city conquest.
///////////////////////////////////////////////////////////////////
// HEADROCK HAM 3.4: Moved the check to a separate function.
if (gGameExternalOptions.gflimitedRoaming)
{
if (!IsSectorRoamingAllowed(pMoveDir[iRandomRes][0]))
return;
}
// WDS bug fix for moving militia
int targetX = SECTORX( pMoveDir[ iRandomRes ][0] );
int targetY = SECTORY( pMoveDir[ iRandomRes ][0] );
Assert(targetX >= 0 && targetX < MAP_WORLD_X);
Assert(targetY >= 0 && targetY < MAP_WORLD_Y);
MoveMilitiaSquad( sMapX, sMapY, targetX, targetY, FALSE );
if ( gfStrategicMilitiaChangesMade)
{
ResetMilitia();
}
if( NumEnemiesInSector( targetX, targetY ) )
{
extern GROUP *gpBattleGroup;
gpBattleGroup = GetGroup( GetEnemyGroupIdInSector( targetX, targetY ) );
/* GROUP* pEnemyGroup = GetGroup( GetEnemyGroupIdInSector( targetX, targetY ) );
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, targetX, 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 = targetX;
pEnemyGroup->ubNextY = targetY;
*/
//Moa: handle deserters before moving in hostile territory
MobileMilitiaDeserters( targetX, targetY, TRUE, TRUE );
gfMSBattle = TRUE;
// GroupArrivedAtSector( pEnemyGroup->ubGroupID , TRUE, FALSE );
EnterAutoResolveMode( targetX, targetY );
// }
}
}
else return;
}
}
}
// Kaiden: Roaming Militia Training:
// If we're training roaming militia,
// we'll get our squad from here:
// Don't need to check for delay, as this function won't be
// 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;
// Variables for checking other sectors in the same city
UINT16 iCounter;
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;
// 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();
gfMSBattle = FALSE;
}
if( CheckInBlockMoveList( sMapX, sMapY ) )return;
// Check all sectors adjacent to the training sector itself.
GenerateDirectionInfosForTraining( sMapX, sMapY, &uiDirNumber, pMoveDir );
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 )
{
// We're training in a city.
iCounter = 0;
// Go through each city in the game
while( pTownNamesList[ iCounter ] != 0 )
{
// 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 ] );
// if sector has enemies or hasn't already been taken at least once, then
if ( !SectorInfo[ SECTOR(sCurrentX, sCurrentY) ].fSurfaceWasEverPlayerControlled ||
NumEnemiesInSector( sCurrentX, sCurrentY ) > 0 )
{
// skip the rest. Generate no militia from this sector.
iCounter++;
continue;
}
// Find out if any adjacent sectors have room in them.
GenerateDirectionInfosForTraining( sCurrentX, sCurrentY, &uiDirNumber, pMoveDir );
if(uiDirNumber)// At least one available adjacent sector has been found
{
// Go through each adjacent sector
for( x = 0; x < uiDirNumber ; x++ )
{
// Have we found the best rating so far?
if ( pMoveDir[x][1] > usBestPerimeterSectorRating )
{
usBestPerimeterSectorID = pMoveDir[x][0];
usBestPerimeterSectorRating = pMoveDir[x][1];
}
}
}
}
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 );
}
}
}
return;
}
extern BOOLEAN gfMSResetMilitia;
void DoMilitiaHelpFromAdjacentSectors( INT16 sMapX, INT16 sMapY )
{
UINT16 pMoveDir[4][3];
UINT8 uiDirNumber = 0;
UINT8 x;
UINT8 uiNumGreen = 0, uiNumReg = 0, uiNumElite = 0;
SECTORINFO *pSectorInfo = &( SectorInfo[ SECTOR( sMapX, sMapY ) ] );
BOOLEAN fMoreTroopsLeft[4] = {FALSE,FALSE,FALSE,FALSE};
BOOLEAN fFirstLoop = TRUE;
BOOLEAN fMilitiaMoved = FALSE;
guiDirNumber = 0;
GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, TRUE );
ZeroMemory( gpAttackDirs, sizeof( gpAttackDirs ) );
gpAttackDirs[ guiDirNumber ][0] = uiNumGreen = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA];
gpAttackDirs[ guiDirNumber ][1] = uiNumReg = pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA];
gpAttackDirs[ guiDirNumber ][2] = uiNumElite = pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
gpAttackDirs[ guiDirNumber ][3] = INSERTION_CODE_CENTER;
guiDirNumber = uiDirNumber + 1;
x = 0;
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 );
if( fMoreTroopsLeft[ x ] )
{
fMilitiaMoved = TRUE;
gpAttackDirs[ x + 1 ][0] += pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA] - uiNumGreen;
gpAttackDirs[ x + 1 ][1] += pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA] - uiNumReg;
gpAttackDirs[ x + 1 ][2] += pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA] - uiNumElite;
gpAttackDirs[ x + 1 ][3] = (UINT8)pMoveDir[ x ][2];
uiNumGreen = pSectorInfo->ubNumberOfCivsAtLevel[GREEN_MILITIA];
uiNumReg = pSectorInfo->ubNumberOfCivsAtLevel[REGULAR_MILITIA];
uiNumElite = pSectorInfo->ubNumberOfCivsAtLevel[ELITE_MILITIA];
}
++x;
if( x >= uiDirNumber )
{
x = 0;
fFirstLoop = FALSE;
}
}
if (is_networked)
{
if (gfStrategicMilitiaChangesMade)
{
RemoveMilitiaFromTactical();
if(is_server && gMilitiaEnabled == 1)
PrepareMilitiaForTactical(FALSE);
}
}
gfStrategicMilitiaChangesMade = FALSE;
}
void MSCallBack( UINT8 ubResult )
{
if( ubResult == MSG_BOX_RETURN_YES )
{
gTacticalStatus.uiFlags |= WANT_MILITIA_REINFORCEMENTS;
DoMilitiaHelpFromAdjacentSectors( sMSMapX, sMSMapY );
}
}
BOOLEAN IsThereMilitiaInAdjacentSector( INT16 sMapX, INT16 sMapY )
{
UINT16 pMoveDir[4][3];
UINT8 uiDirNumber = 0;
UINT8 x;
BOOLEAN fResult = FALSE;
GenerateDirectionInfos( sMapX, sMapY, &uiDirNumber, pMoveDir, FALSE, TRUE );
for( x = 0; x < uiDirNumber ; ++x )
if( CountAllMilitiaInSector( SECTORX( pMoveDir[ x ][ 0 ] ), SECTORY( pMoveDir[ x ][ 0 ] ) ) )
fResult = TRUE;
return fResult;
}
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;
gTacticalStatus.uiFlags &= (~WANT_MILITIA_REINFORCEMENTS);
guiDirNumber = 0;
// if( CountAllMilitiaInSector( sMapX, sMapY ) ) MSCallBack( MSG_BOX_RETURN_YES );
// 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 )
{
// 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 )
{
if( GetTownIdForSector( sMapX, sMapY ) != BLANK_SECTOR ||
IsThisSectorASAMSector( sMapX, sMapY, 0 ) ||
PlayerMercsInSector_MSE( (UINT8)sMapX, (UINT8)sMapY, TRUE ) )return;
if( GetTownIdForSector( sDMapX, sDMapY ) != BLANK_SECTOR ||
IsThisSectorASAMSector( sDMapX, sDMapY, 0 ) )return;
MoveMilitiaSquad( sMapX, sMapY, sDMapX, sDMapY, FALSE );
}
// HEADROCK HAM 5: New flag tells us to also recheck restriced sectors.
//Moa: changed the flag behavier - makes no sense to overwrite user settings, instead I reusing it to correct wrong values in old savegames.
// 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( BOOLEAN fRecheck )
{
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].sSectorID >= 0); cnt++)
{
if (!(~uiCapturedTownsFlag & gDynamicRestrictions[cnt].uiReqTownFlags))
{
gDynamicRestrictMilitia[gDynamicRestrictions[cnt].sSectorID] = true;
// HEADROCK HAM 4: Also adjust Player-based restrictions.
if (gubManualRestrictMilitia[ gDynamicRestrictions[cnt].sSectorID ] == MANUAL_MOBILE_RESTRICTED)
{
gubManualRestrictMilitia[ gDynamicRestrictions[cnt].sSectorID ] = MANUAL_MOBILE_NO_RESTRICTION;
}
}
}
// HEADROCK HAM 5: All restricted sectors are checked to see they aren't manually-permitted.
//Moa: Dont ever touch user settings, player set the restrictions for a reason. Initialization allready in InitManualMobileRestrictions.
//dnl!!! However if we are loading an older save we might need to check the array for MANUAL_MOBILE_RESTRICTED and replace it with MANUAL_MOBILE_NO_ENTER
//actually this can be removed once there are no older savegames around :)
if (fRecheck)
{
for (cnt = 0; cnt < 256; cnt++)
{
if (gubManualRestrictMilitia[cnt] == MANUAL_MOBILE_RESTRICTED)
{
gubManualRestrictMilitia[cnt] = MANUAL_MOBILE_NO_ENTER;
}
}
}
}
// 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. The stored results
// indicate the amount of space left in the target sector.
// HEADROCK HAM 3.6: Revamped to fix bugs.
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
// Check north
if( sMapY > MINIMUM_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_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 )
{
// HEADROCK HAM 4: Take player-based restrictions into account...
if ( gubManualRestrictMilitia[ uiSector ] == MANUAL_MOBILE_NO_RESTRICTION ||
gubManualRestrictMilitia[ uiSector ] == MANUAL_MOBILE_NO_LEAVE )
{
// Always return TRUE.
return TRUE;
}
}
}
// Please note that with dynamic 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;
}
}
else
{
// HEADROCK HAM 4: Take player restrictions into account
if ( gubManualRestrictMilitia[ uiSector ] == MANUAL_MOBILE_NO_ENTER )
{
if ( !NumEnemiesInSector( SECTORX(uiSector), SECTORY(uiSector) ) )
{
// The player has disallowed movement into this sector, manually. As the condition above
// dictates, this only takes effect if there are no enemies in the target sector. Otherwise,
// militia can move to intercept them anyway.
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);
}
extern BOOLEAN SectorIsImpassable( INT16 sSector );
// HEADROCK HAM 4: Returns whether sector is allowed for militia roaming, taking into account player-set restrictions.
UINT8 ManualMobileMovementAllowed( UINT8 ubSector )
{
BOOLEAN fcheckManualSettings = FALSE;
//if no one can pass get out of here quick!
if ( SectorIsImpassable( ubSector ) )
return MANUAL_MOBILE_RESTRICTED;
if (gGameExternalOptions.gflimitedRoaming)//RESTRICT_ROAMING = TRUE
{
if ( SectorInfo[ ubSector ].fSurfaceWasEverPlayerControlled )// Has the sector ever been liberated?
{
if (gGameExternalOptions.fUnrestrictVisited)//ALLOW_MILITIA_MOVEMENT_THROUGH_EXPLORED_SECTORS = TRUE
{
// Always check for manual restrictions.
fcheckManualSettings = TRUE;
}
else if (gGameExternalOptions.fAllowMilitiaMoveThroughMinorCities)//ALLOW_MILITIA_MOVEMENT_THROUGH_MINOR_CITIES = TRUE
{
UINT8 townID = GetTownIdForSector( SECTORX(ubSector), SECTORY(ubSector) );
//this is a city and its a minor one where training is not allowed like San Mona, Tixa, Orta, Estoni, or Omerta?
if (townID != BLANK_SECTOR && !gfMilitiaAllowedInTown[townID])
fcheckManualSettings = TRUE;
}
}
if(gGameExternalOptions.fDynamicRestrictRoaming)//ALLOW_DYNAMIC_RESTRICTED_ROAMING = TRUE
{
fcheckManualSettings |= gDynamicRestrictMilitia[ ubSector ];
}
}
else//RESTRICT_ROAMING = FALSE
{
// No restrictions, militia is free to go (green)
return MANUAL_MOBILE_NO_RESTRICTION;
}
if (fcheckManualSettings)
{
// player has restricted manually (green,yellow,red)
#ifdef JA2BETAVERSION
AssertGE(gubManualRestrictMilitia[ ubSector ],0);
AssertLE(gubManualRestrictMilitia[ ubSector ],3);
#endif
return gubManualRestrictMilitia[ ubSector ];
}
else
{
// Restricted roaming! Militia can't go here ever. (gray)
return MANUAL_MOBILE_RESTRICTED;
}
}
void InitManualMobileRestrictions()
{
UINT16 x;
for ( x = 0; x < 256; x++ )
{
UINT8 ubTownID = GetTownIdForSector( SECTORX(x), SECTORY(x) );
if (ubTownID != BLANK_SECTOR )
{
if (gfMilitiaAllowedInTown[ubTownID])
{
// Major town. Set to NO-ENTER by default.
gubManualRestrictMilitia[x] = MANUAL_MOBILE_NO_ENTER;
continue;
}
else
{
// Minor town. Set to NO_RESTRICTIONS by default.
gubManualRestrictMilitia[x] = MANUAL_MOBILE_NO_RESTRICTION;
continue;
}
}
// By default, all other sectors are "go".
gubManualRestrictMilitia[x] = MANUAL_MOBILE_NO_RESTRICTION;
}
}
extern BOOLEAN IsMercOnTeam(UINT8 ubMercID);
// @brief Calculates the contingent for mobile militia.
// Calculates current active mobile militia and compares to variable maximum limit which can be altered by external data.
// @param printMessage Set to TRUE to get a screenmessage telling the player which maximum is reached. No message is generated when the maximum is not yet reached (<=100).
// @return The current percentage (0% - 255%). If the feature is deactivated allways 0 is returned.
UINT8 GetMobileMilitiaQuota( BOOLEAN printMessage )
{
///////////////////////
// Check for maximum mobiles allowed
//
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode > 1 )
{
//what we need to know
UINT16 iActiveMobiles = 0, iCurrentMax = 0;
UINT16 iTownSectorsUnderPlayerControl = 0, iTownSectorsLiberatedAtLeastOnce = 0;
UINT8 iTownsUnderPlayerControl = 0, iCurrentProgress = 0, iMaxProgress = 0, iNumRebelsInPlayerTeam = 0;
//temp values for sectors we gonna check
INT8 iCurrT_ID, test;
UINT16 iStrategicMapID, iSectorInfoID;
SECTORINFO *pSectorInfo;
// start gathering data
if (gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 8U) // check for TownsLiberated is active
for ( iCurrT_ID = 0; iCurrT_ID < NUM_TOWNS; iCurrT_ID++) // for each town
{
if ( GetTownSectorsUnderControl( iCurrT_ID ) == GetTownSectorSize( iCurrT_ID ) )
iTownsUnderPlayerControl++; // remember player has that town under control
}
for ( iSectorInfoID = 0; iSectorInfoID < 255; iSectorInfoID++) // for each sector ...
{
iStrategicMapID = SECTOR_INFO_TO_STRATEGIC_INDEX(iSectorInfoID);
pSectorInfo = &( SectorInfo[ iSectorInfoID ] );
iCurrT_ID = StrategicMap[ iStrategicMapID ].bNameId;
if ( iCurrT_ID != BLANK_SECTOR )
{
// we have found a town (no sam site)
if ( StrategicMap[ iStrategicMapID ].fEnemyControlled == FALSE )
iTownSectorsUnderPlayerControl++; // remember player currently controls that town sector
if ( pSectorInfo->fSurfaceWasEverPlayerControlled == TRUE )
iTownSectorsLiberatedAtLeastOnce++; // remember player had allready liberated that town sector once
if ( MilitiaTrainingAllowedInTown( iCurrT_ID ) == FALSE ) // considered as mobile only if in that town training is taboo (Tixa, Omerta..)
iActiveMobiles += CountMilitia( pSectorInfo );
//for ( UINT8 militiaLevel = 0; militiaLevel < MAX_MILITIA_LEVELS; militiaLevel++ )
// iActiveMobiles += pSectorInfo->ubNumberOfCivsAtLevel[ militiaLevel ]; // remember number of roaming militia in that town sector (green, regular, elite, ..)
}
//else if ( StrategicMap[ iStrategicMapID ].bSAMCondition > 0 ) //faster then below, but not safe
else if ( MilitiaTrainingAllowedInSector( SECTORX(iSectorInfoID), SECTORY(iSectorInfoID), 0 ) == FALSE )
{
// we are in wilderness
//for ( UINT8 militiaLevel = 0; militiaLevel < MAX_MILITIA_LEVELS; militiaLevel++ )
// iActiveMobiles += pSectorInfo->ubNumberOfCivsAtLevel[ militiaLevel ]; // remember number of roaming militia in that sector (green, regular, elite, ..)
iActiveMobiles += CountMilitia( pSectorInfo );
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 64U ) // check for rebels in player team is active
{
if ( gProfilesRPC != NULL )
{
UINT16 rebelStructSize = sizeof ( gProfilesRPC );
for (UINT16 rebelPC = 0; rebelPC < rebelStructSize; rebelPC++)
{
if ( IsMercOnTeam ( gProfilesRPC[ rebelPC ].ProfilId ) ) iNumRebelsInPlayerTeam++;
}
}
//if ( IsMercOnTeam ( IRA ) ) iNumRebelsInPlayerTeam++;
//if ( IsMercOnTeam ( DIMITRI ) ) iNumRebelsInPlayerTeam++;
//if ( IsMercOnTeam ( CARLOS ) ) iNumRebelsInPlayerTeam++;
//if ( IsMercOnTeam ( MIGUEL ) ) iNumRebelsInPlayerTeam++;
//if ( IsMercOnTeam ( DYNAMO ) ) iNumRebelsInPlayerTeam++;
//if ( IsMercOnTeam ( ENRICO ) ) iNumRebelsInPlayerTeam++;
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 16U ) // check for current progress is active
iCurrentProgress = CurrentPlayerProgressPercentage();
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 32U ) // check for max progress is active
iMaxProgress = HighestPlayerProgressPercentage();
//end gathering data
/////////////////////
// calc maximum (note: the modifier was initilized allready with MOBILE_MILITIA_MAX_ACTIVE_MODIFIER * MAX_MILITIA_PER_SECTOR)
UINT8 iModeMatch = 0; //takes the mode which matches the maximum used in order to build a message later on (0=no match 1..6)
UINT16 iCalc;
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 128U ) // we are using the highest value
{
iCurrentMax = 0;
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 2U ) // check for town sectors under player control is active
{
iCalc = (UINT16) ( iTownSectorsUnderPlayerControl * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier );
if ( iCurrentMax <= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 1;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 4U ) // check for town sectors liberated at least once is active
{
iCalc = (UINT16) (iTownSectorsLiberatedAtLeastOnce * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier );
if ( iCurrentMax <= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 2;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 8U )
{
iCalc = (UINT16) (iTownsUnderPlayerControl * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 3 );
if ( iCurrentMax <= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 3;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 16U )
{
iCalc = (UINT16) (iCurrentProgress * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 20 / 100 );
if ( iCurrentMax <= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 4;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 32U )
{
iCalc = (UINT16) (iMaxProgress * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 20 / 100 );
if ( iCurrentMax <= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 5;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 64U )
{
iCalc = (UINT16) (iNumRebelsInPlayerTeam * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 3 );
if ( iCurrentMax <= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 6;
}
}
}
else // we are using lowest value
{
iCurrentMax = (UINT16) -1;
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 2U )
{
iCalc = (UINT16) ( iTownSectorsUnderPlayerControl * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier );
if ( iCurrentMax >= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 1;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 4U )
{
iCalc = (UINT16) (iTownSectorsLiberatedAtLeastOnce * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier );
if ( iCurrentMax >= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 2;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 8U )
{
iCalc = (UINT16) (iTownsUnderPlayerControl * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 3 );
if ( iCurrentMax >= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 3;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 16U )
{
iCalc = (UINT16) (iCurrentProgress * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 20 / 100 );
if ( iCurrentMax >= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 4;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 32U )
{
iCalc = (UINT16) (iMaxProgress * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 20 / 100 );
if ( iCurrentMax >= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 5;
}
}
if ( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 64U )
{
iCalc = (UINT16) (iNumRebelsInPlayerTeam * gGameExternalOptions.gfpMobileMilitiaMaxActiveModifier * 3 );
if ( iCurrentMax >= iCalc )
{
iCurrentMax = iCalc;
iModeMatch = 6;
}
}
}
/////////////////////
//Notify player
if ( iActiveMobiles >= iCurrentMax )
{
if ( printMessage && iModeMatch > 0 )//fail safe to avoid in-buffer == out-buffer
{
// providing feedback why player cant train more.
CHAR16 sString[200];
swprintf( sString, pMilitiaConfirmStrings[15], iActiveMobiles, iCurrentMax, pMilitiaConfirmStrings[15 + iModeMatch] );//We reached maximum (..active../..max..)..make this..to do sth
//ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, sString );
DoScreenIndependantMessageBox(sString, MSG_BOX_FLAG_OK, NULL);
}
}
return ( iCurrentMax == 0 ? 255 : (iActiveMobiles * 100 / iCurrentMax) );
//return ( iCurrentMax == 0 ? 255 : max( 255, (iActiveMobiles * 100 / iCurrentMax) ) );
}//end check for maximum mobile militia allowed
return 0;
}
//@brief Checks for militia quota and removes randomly some of militia if there are too many.
// Deserting works only if the feature is enabled and the sector is threatened by an adjacent enemy presence.
// The chance to generate deserters raises with the quota.
//@param sMapX, sMapY Strategic sector coordinate which will be checked.
//@param fDeleteEquipment If the militia has any equipment it will be deleted when he is a deserter.
//@param fPrintMessage Notifies player that some militia have deserted.
//@auth Moa
void MobileMilitiaDeserters(INT16 sMapX, INT16 sMapY, BOOLEAN fDeleteEquip, BOOLEAN fPrintMessage)
{
// if more then allowed mobiles are active some of them will desert (green and only some regulars, elites will never desert)
UINT8 desertersGreen = 0, desertersRegular = 0;
BOOLEAN enemiesNear = FALSE;
// feature not activated, return
if (!( gGameExternalOptions.gbMobileMilitiaMaxActiveMode & 1U ) )
return;
// Training allowed here therefore not a sector for mobile militia, return
if ( gfMilitiaAllowedInTown [ GetTownIdForSector( sMapX, sMapY ) ] )
return;
// This is a SAM site therefore no mobile militia here, return
if (IsThisSectorASAMSector( sMapX, sMapY, 0 ))
return;
///////////////////////
// check enemy presence to decide if mobiles are threatened and some will desert.
// Note: cant use NumEnemiesInFiveSectors() as it does not work for omerta or when reinforcements are deactivated, also it uses generateDirectionInfo(), which is used for militia movement.
UINT8 eAdmins = 0, eTroops = 0, eElites = 0;
//enemies in current sector
GetNumberOfEnemiesInSector( sMapX, sMapY, &eAdmins, &eTroops, &eElites );
if ( (eAdmins > 0) || (eTroops > 0) ||(eElites > 0) )
enemiesNear = TRUE;
if ( ( sMapX > MINIMUM_VALID_X_COORDINATE ) && !enemiesNear )
{
//left side
GetNumberOfEnemiesInSector( sMapX - 1, sMapY, &eAdmins, &eTroops, &eElites );
if ( (eAdmins > 0) || (eTroops > 0) ||(eElites > 0) )
enemiesNear = TRUE;
}
if ( ( sMapX < MAXIMUM_VALID_X_COORDINATE ) && !enemiesNear)
{
//right side
GetNumberOfEnemiesInSector( sMapX + 1, sMapY, &eAdmins, &eTroops, &eElites );
if ( (eAdmins > 0) || (eTroops > 0) ||(eElites > 0) )
enemiesNear = TRUE;
}
if ( ( sMapY > MINIMUM_VALID_Y_COORDINATE ) && !enemiesNear )
{
//top side
GetNumberOfEnemiesInSector( sMapX, sMapY - 1, &eAdmins, &eTroops, &eElites );
if ( (eAdmins > 0) || (eTroops > 0) ||(eElites > 0) )
enemiesNear = TRUE;
}
if ( ( sMapY < MAXIMUM_VALID_Y_COORDINATE ) && !enemiesNear)
{
//bottom side
GetNumberOfEnemiesInSector( sMapX, sMapY + 1, &eAdmins, &eTroops, &eElites );
if ( (eAdmins > 0) || (eTroops > 0) ||(eElites > 0) )
enemiesNear = TRUE;
}
/////////////////////////
//calc number of deserters
if ( enemiesNear ) //if deserting feature is active, this is a sector where mobile militia is allowed and there is a threat at target
{
UINT8 quota = GetMobileMilitiaQuota( FALSE );
if (quota > 100) //more active then allowed
{
UINT8 militiaGreen = SectorInfo[ SECTOR( sMapX, sMapY) ].ubNumberOfCivsAtLevel[ GREEN_MILITIA ];
UINT8 militiaRegular = SectorInfo[ SECTOR( sMapX, sMapY) ].ubNumberOfCivsAtLevel[ REGULAR_MILITIA ];
desertersGreen = (quota - 100) * militiaGreen / 100;
desertersGreen = Random( min( militiaGreen, desertersGreen ) );//cant remove more then actually exist in that group
desertersRegular = (quota - 100) * militiaRegular / 200;
desertersRegular = Random( min ( militiaRegular, desertersRegular ) );
}
}
////////////////////////
//remove militia
if ( desertersRegular + desertersGreen > 0 )
{
StrategicRemoveMilitiaFromSector( sMapX, sMapY, GREEN_MILITIA, desertersGreen );
StrategicRemoveMilitiaFromSector( sMapX, sMapY, REGULAR_MILITIA, desertersRegular );
///////////////////////////
//remove equipment
if ( fDeleteEquip && gGameExternalOptions.fMilitiaUseSectorInventory)
{
SOLDIERCREATE_STRUCT trashIt;
UINT8 cnt = desertersGreen;
while ( cnt > 0 )
{
TakeMilitiaEquipmentfromSector( sMapX, sMapY, 0, &trashIt, SOLDIER_CLASS_GREEN_MILITIA);
cnt--;
}
cnt = desertersRegular;
while ( cnt > 0 )
{
TakeMilitiaEquipmentfromSector( sMapX, sMapY, 0, &trashIt, SOLDIER_CLASS_REG_MILITIA);
cnt--;
}
}
///////////////////////////
//notify player
if ( fPrintMessage )
{
CHAR16 sSector[16];
GetShortSectorString( sMapX, sMapY, sSector );
//using screen message to have it logged. Cant use msgBox as it prevents showing the autoresolve screen.
ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pMilitiaConfirmStrings[ 22 ], ( desertersRegular + desertersGreen ), sSector );
}
}
//clean up
if (gfStrategicMilitiaChangesMade)
{
ResetMilitia();
}
}