Files
source/Strategic/Strategic Town Loyalty.cpp
fd2c74727e Make LANGUAGE switchable in runtime (#653)
squashed into a single commit because doing it in steps that all
compiled involved a lot of boilerplate that is pointless to commit to
the repository. GH still has the PR for reference.

g_lang, MAX_MESSAGES_ON_MAP_BOTTOM, and GetLanguagePrefix() were
compile-time constants selected by the ENGLISH/GERMAN/... build
define. They're now runtime, defaulting to the same per-exe value the
define used to pick, and overridable at startup from [Ja2 Settings]
LANGUAGE in Ja2.ini.

-

XMLTacticalMessages is filled at runtime from NewTacticalMessages.xml,
never from compiled-in per-language data: all 8 per-language definitions
were the identical all-zero { L"" }. Delete them and define one shared
buffer in Utils/XML_Language.cpp (the loader) instead of pointer-rebinding
it like the static tables — this drops 9 dead 800KB zero-buffers (8
namespaced copies in LanguageStrings.cpp plus the standalone one) and
leaves no bind-ordering hazard for the XML load path.

-

ExportStrings.cpp privately recompiled one language's full text table
by #including the raw _<LANG>Text.cpp inside namespace Loc, keyed off
the exe-level ENGLISH/GERMAN/... compile macro (whichever the build
happened to select). That's redundant with the pointer globals every
other subsystem already uses (Text.h / LanguageStrings.cpp).

Drop the private copy; the unqualified table names in Loc::ExportStrings
now resolve to the global pointer externs, which BindLanguageStrings has
already rebound to the runtime g_lang by the time this runs (EXPORT_STRINGS
ini flag, checked after GetRuntimeSettings in sgp.cpp). gs_Lang (used only
by Loc::Translate for the Polish/Russian byte remap on raw .edt exports) is
now derived from g_lang via ToLocLanguage, so the export always matches
whatever language is actually active instead of a compile-time pick.

-

Editor/popupmenu.cpp and Strategic/Scheduling.cpp kept their own
call-site extern of gszScheduleActions as CHAR16[NUM_SCHEDULE_ACTIONS][20]
after the real definition changed into a rebindable pointer
(CHAR16 (*)[20], LanguageStrings.cpp). MSVC decays the outer array
dimension when mangling globals, so both declarations produce the same
symbol (?gszScheduleActions@@3PAY0BE@_WA) and the mismatch linked
silently -- but the array-typed TUs then indexed the 4-byte pointer
slot itself as string data, so the editor schedule popup and the map
schedule message text read garbage.

-

add text.def to be single-source of truth on symbol names and use it
 .much simpler, less error prone if adding more strings

-

add pseudo interface for language state. MAX_SAGES_ON_BOTTOM must always
 change in lockstep with g_lang. this isn't foolproof but better

---------

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-07-21 05:33:03 -03:00

2239 lines
67 KiB
C++

#include "builddefines.h"
#include "Strategic Town Loyalty.h"
#include "strategicmap.h"
#include "Overhead.h"
#include "Assignments.h"
#include "strategic.h"
#include "Queen Command.h"
#include "Animation Data.h"
#include "Quests.h"
#include "message.h"
#include "LOS.h"
#include "World Items.h"
#include "Tactical Save.h"
#include "Soldier Profile.h"
#include "Handle Items.h"
#include "random.h"
#include "Strategic Movement.h"
#include "Strategic Pathing.h"
#include "Game Clock.h"
#include "Morale.h"
#include "GameSettings.h"
#include "Text.h"
#include "MessageBoxScreen.h"
#include "Town Militia.h"
#include "history.h"
#include "Meanwhile.h"
#include "Strategic Status.h"
#include "Facilities.h"
#include "CampaignStats.h" // added by Flugente
#include "DynamicDialogue.h" // added by Flugente
#include "Rebel Command.h"
#include "Luaglobal.h"
#include "LuaInitNPCs.h"
#include "Interface.h"
#include <language.hpp>
#include "GameInitOptionsScreen.h"
extern WorldItems gAllWorldItems;
// loyalty Omerta drops to and maxes out at if the player betrays the rebels
#define HOSTILE_OMERTA_LOYALTY_RATING 10
// the loyalty threshold below which theft of unsupervised items in a town sector can occur
#define THRESHOLD_FOR_TOWN_THEFT 50
#define LOYALTY_EVENT_DISTANCE_THRESHOLD 3 // in sectors
// effect of unintentional killing /100 vs intentional murder
#define REDUCTION_FOR_UNINTENTIONAL_KILLING 50
// the effect of a murder by rebel / 100 vs player murdering a civ
#define REDUCTION_FOR_MURDER_BY_REBEL 70
// reduction % for being falsely blamed for a murder we didn't do, out of 100
#define REDUCTION_FOR_MURDER_NOT_OUR_FAULT 50
// reduction % if enemies kills civs in our sector / 100
#define REDUCTION_FOR_MURDER_OF_INNOCENT_BY_ENEMY_IN_OUR_SECTOR 25
// how much worse loyalty hit is for trauma of someone killed by a monster
#define MULTIPLIER_FOR_MURDER_BY_MONSTER 2
// gain for hiring an NPC from a particular town (represents max. at 100% town attachment)
#define MULTIPLIER_LOCAL_RPC_HIRED 25 // 5%
// multiplier for causing pts of damage directly done to a building
#define MULTIPLIER_FOR_DAMAGING_A_BUILDING 10 // 50 pts = 1%
// multiplier for not preventing pts of damage to a building
#define MULTIPLIER_FOR_NOT_PREVENTING_BUILDING_DAMAGE 3 // 167 pts = 1%
// divisor for dmg to a building by allied rebel
#define DIVISOR_FOR_REBEL_BUILDING_DMG 2
/*
// max number of soldiers counted towards town loyalty gain
#define MAX_SOLDIER_COUNT_FOR_IN_TOWN_LOYALTY_GAIN 6
// max levels of town militia civs counted towards town loyalty gain
#define MAX_MILITIA_VALUE_FOR_IN_TOWN_LOYALTY_GAIN 18
// max # occupying enemy ranks that count for loyalty penalty
#define MAX_ENEMY_SOLDIER_RANKS_FOR_IN_TOWN_LOYALTY_DROP 15
// number of ranks counted per regular
#define NUMBER_OF_RANKS_PER_REGULAR 2
// number of ranks counted per elite
#define NUMBER_OF_RANKS_PER_ELITE 4
// THESE VALUES ARE ALL AFFECTED BY CHANGES to "GAIN_PTS_PER_LOYALTY_PT", SO BEWARE IF THAT SHOULD CHANGE
//
// max. rate of loyalty gain for local RPC being stationed in his home town
#define HOURLY_GAIN_FOR_LOCAL_NPC_IN_TOWN 20 // roughly 1.00% / day max.
// number of gain pts per merc in town
#define HOURLY_GAIN_PER_MERC_IN_TOWN 5 // roughly 0.25% / day
// number of gain pts per friendly in town
#define HOURLY_GAIN_PER_MILITIA_IN_TOWN 1 // roughly 0.05% / day
// loyalty loss for enemies in town
#define HOURLY_DROP_PER_ENEMY_RANK_IN_TOWN 2 // roughly 0.10% / day
*/
//
// THESE VALUES ARE ALL AFFECTED BY CHANGES to "GAIN_PTS_PER_LOYALTY_PT", SO BEWARE IF THAT SHOULD CHANGE
/* Delayed loyalty effects elimininated. Sep.12/98. ARM
// macros for delayed town loyalty events
// get increment
#define GET_TOWN_INCREMENT( iValue ) ( ( uiValue << 31 ) >> 31 )
// town id
#define GET_TOWN_ID_FOR_LOYALTY( iValue ) ( uiValue >> 24 )
// the amount
#define GET_TOWN_LOYALTY_CHANGE( iValue ) ( ( uiValue << 8 ) >> 9 )
*/
// town loyalty table
TOWN_LOYALTY gTownLoyalty[ MAX_TOWNS ];
// town name and locations arrays, for town theft and what not
INT32 pTownNamesList [ MAX_TOWN_SECTORS ];
INT32 pTownLocationsList [ MAX_TOWN_SECTORS ];
INT32 iTownDistances[ MAX_TOWNS ][ MAX_TOWNS ];
#define BASIC_COST_FOR_CIV_MURDER (10 * GAIN_PTS_PER_LOYALTY_PT)
UINT32 uiPercentLoyaltyDecreaseForCivMurder[]={
// These get multiplied by GAIN_PTS_PER_LOYALTY_PT so they're in % of loyalty decrease (for an average town)
(5 * GAIN_PTS_PER_LOYALTY_PT), // fat civ
(7 * GAIN_PTS_PER_LOYALTY_PT), // man civ
(8 * GAIN_PTS_PER_LOYALTY_PT), // min civ
(10 * GAIN_PTS_PER_LOYALTY_PT), // dress (woman)
(20 * GAIN_PTS_PER_LOYALTY_PT), // hat kid
(20 * GAIN_PTS_PER_LOYALTY_PT), // kid
(20 * GAIN_PTS_PER_LOYALTY_PT), // cripple
(1 * GAIN_PTS_PER_LOYALTY_PT), // cow
};
// on a scale of 1-100, this is a measure of how much each town hates the Queen's opression & is willing to stand against it
// it primarily controls the RATE of loyalty change in each town: the loyalty effect of the same events depends on it
UINT8 gubTownRebelSentiment[ MAX_TOWNS ]; // =
//{
// 0, // not a town - blank sector index
// 90, // OMERTA, - They ARE the rebels!!!
// 30, // DRASSEN, - Rebel friendly, makes it pretty easy to get first mine's income going at the start
// 12, // ALMA - Military town, high loyalty to Queen, need quests to get 100%
// 15, // GRUMM, - Close to Meduna, strong influence
// 20, // TIXA, - Not a real town
// 15, // CAMBRIA, - Artificially much lower 'cause it's big and central and too easy to get loyalty up there
// 20, // SAN_MONA,- Neutral ground, loyalty doesn't vary
// 20, // ESTONI, - Not a real town
// 20, // ORTA, - Not a real town
// 12, // BALIME, - Rich town, high loyalty to Queen
// 10, // MEDUNA, - Enemy HQ, for God's sake!
// 35, // CHITZENA, - Artificially high 'cause there's not enough fights near it to get the loyalty up otherwise
//};
BOOLEAN gfTownUsesLoyalty[ MAX_TOWNS ];// =
//{
// FALSE, // not a town - blank sector index
// TRUE , // OMERTA
// TRUE, // DRASSEN
// TRUE, // ALMA
// TRUE, // GRUMM
// FALSE, // TIXA
// TRUE, // CAMBRIA
// FALSE, // SAN_MONA
// FALSE, // ESTONI
// FALSE, // ORTA
// TRUE, // BALIME
// TRUE, // MEDUNA
// TRUE, // CHITZENA
//};
// location of first enocunter with enemy
INT16 sWorldSectorLocationOfFirstBattle = 0;
// preprocess sector for mercs in it
extern BOOLEAN fSectorsWithSoldiers[ MAP_WORLD_X * MAP_WORLD_X ][ 4 ];
extern CHAR16 (*pTownNames)[MAX_TOWN_NAME_LENGHT];
// update the loyalty rating of the passed town id
void UpdateTownLoyaltyRating( INT8 bTownId );
// update town loyalty based on number of friendlies in this town
void UpdateTownLoyaltyBasedOnFriendliesInTown( INT8 bTownId );
// update town loyalty based on number of bad guys in this town
void UpdateTownLoyaltyBasedOnBadGuysInTown( INT8 bTownId );
/* ARM: Civilian theft of items was removed
// handle theft by civi in a town sector
void HandleTheftByCiviliansInSector( INT16 sX, INT16 sY, INT32 iLoyalty );
// handle theft in all towns
void HandleTownTheft( void );
*/
extern void MapScreenDefaultOkBoxCallback( UINT8 bExitValue );
extern void CheckConditionsForTriggeringCreatureQuest( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ );
void InitTownLoyalty( void )
{
UINT8 ubTown = 0;
// set up town loyalty table
for( ubTown = FIRST_TOWN; ubTown < NUM_TOWNS; ubTown++ )
{
gTownLoyalty[ ubTown ].ubRating = 0;
gTownLoyalty[ ubTown ].sChange = 0;
gTownLoyalty[ ubTown ].fStarted = FALSE;
// gTownLoyalty[ ubTown ].ubRebelSentiment = gubTownRebelSentiment[ ubTown ];
gTownLoyalty[ ubTown ].fLiberatedAlready = FALSE;
}
return;
}
void StartTownLoyaltyIfFirstTime( INT8 bTownId )
{
Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
// if loyalty tracking hasn't yet been started for this town, and the town does use loyalty
if (!gTownLoyalty[ bTownId ].fStarted && gfTownUsesLoyalty[ bTownId ])
{
// set starting town loyalty now, equally to that town's current rebel sentiment - not all towns begin equally loyal
gTownLoyalty[ bTownId ].ubRating = gubTownRebelSentiment[ bTownId ];
// if player hasn't made contact with Miguel yet, or the rebels hate the player
if (!CheckFact(FACT_MIGUEL_READ_LETTER, 0) || CheckFact( FACT_REBELS_HATE_PLAYER, 0 ))
{
// if town is Omerta
if (bTownId == OMERTA)
{
// start loyalty there at 0, since rebels distrust the player until Miguel receives the letter
gTownLoyalty[ bTownId ].ubRating = 0;
}
else
{
// starting loyalty is halved - locals not sure what to make of the player's presence
gTownLoyalty[ bTownId ].ubRating /= 2;
}
}
gTownLoyalty[ bTownId ].sChange = 0;
// remember we've started
gTownLoyalty[ bTownId ].fStarted = TRUE;
}
}
// set a specified town's loyalty rating (ignores previous loyalty value - probably NOT what you want)
void SetTownLoyalty( INT8 bTownId, UINT8 ubNewLoyaltyRating )
{
Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
// if the town does use loyalty
if (gfTownUsesLoyalty[ bTownId ])
{
gTownLoyalty[ bTownId ].ubRating = ubNewLoyaltyRating;
gTownLoyalty[ bTownId ].sChange = 0;
// this is just like starting the loyalty if it happens first
gTownLoyalty[ bTownId ].fStarted = TRUE;
}
return;
}
// increments the town's loyalty rating by that many HUNDREDTHS of loyalty pts
void IncrementTownLoyalty( INT8 bTownId, UINT32 uiLoyaltyIncrease )
{
UINT32 uiRemainingIncrement;
INT16 sThisIncrement;
AssertGE (bTownId, BLANK_SECTOR);
AssertLT (bTownId, NUM_TOWNS);
// doesn't affect towns where player hasn't established a "presence" yet
if (!gTownLoyalty[ bTownId ].fStarted)
{
return;
}
// if rebel command is enabled, apply gain modifier
uiLoyaltyIncrease *= RebelCommand::GetLoyaltyGainModifier();
// modify loyalty change by town's individual attitude toward rebelling (20 is typical)
uiLoyaltyIncrease *= (5 * gubTownRebelSentiment[ bTownId ]);
uiLoyaltyIncrease /= 100;
// this whole thing is a hack to avoid rolling over the -32 to 32k range on the sChange value
// only do a maximum of 10000 pts at a time...
uiRemainingIncrement = uiLoyaltyIncrease;
while ( uiRemainingIncrement )
{
sThisIncrement = ( INT16 ) min( uiRemainingIncrement, 10000 );
// up the gain value
gTownLoyalty[ bTownId ].sChange += (INT16) sThisIncrement;
// update town value now
UpdateTownLoyaltyRating( bTownId );
uiRemainingIncrement -= sThisIncrement;
}
return;
}
// decrements the town's loyalty rating by that many HUNDREDTHS of loyalty pts
//NOTE: This function expects a POSITIVE number for a decrease!!!
void DecrementTownLoyalty( INT8 bTownId, UINT32 uiLoyaltyDecrease )
{
UINT32 uiRemainingDecrement;
INT16 sThisDecrement;
Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
// doesn't affect towns where player hasn't established a "presence" yet
if (!gTownLoyalty[ bTownId ].fStarted)
{
return;
}
// anv: snitches spreading propaganda affect decrease amount
uiLoyaltyDecrease = HandlePropagandaBlockingBadNewsInTown( bTownId, uiLoyaltyDecrease );
// modify loyalty change by town's individual attitude toward rebelling (20 is typical)
uiLoyaltyDecrease *= 100;
uiLoyaltyDecrease /= (5 * gubTownRebelSentiment[ bTownId ]);
// this whole thing is a hack to avoid rolling over the -32 to 32k range on the sChange value
// only do a maximum of 10000 pts at a time...
uiRemainingDecrement = uiLoyaltyDecrease;
while ( uiRemainingDecrement )
{
sThisDecrement = ( INT16 ) min( uiRemainingDecrement, 10000 );
// down the gain value
gTownLoyalty[ bTownId ].sChange -= sThisDecrement;
// update town value now
UpdateTownLoyaltyRating( bTownId );
uiRemainingDecrement -= sThisDecrement;
}
}
// update town loyalty rating based on gain values
void UpdateTownLoyaltyRating( INT8 bTownId )
{
// check gain value and update loyaty
UINT8 ubOldLoyaltyRating = 0;
INT16 sRatingChange = 0;
UINT8 ubMaxLoyalty = 0;
Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
// remember previous loyalty value
ubOldLoyaltyRating = gTownLoyalty[ bTownId ].ubRating;
sRatingChange = gTownLoyalty[ bTownId ].sChange / GAIN_PTS_PER_LOYALTY_PT;
// if loyalty is ready to increase
if( sRatingChange > 0 )
{
// if the town is Omerta, and the rebels are/will become hostile
if ((bTownId == OMERTA) && (gTacticalStatus.fCivGroupHostile[ REBEL_CIV_GROUP ] != CIV_GROUP_NEUTRAL))
{
// maximum loyalty is much less than normal
ubMaxLoyalty = HOSTILE_OMERTA_LOYALTY_RATING;
}
else
{
ubMaxLoyalty = MAX_LOYALTY_VALUE;
}
// if playing with rebel command, limit max loyalty
ubMaxLoyalty = min(ubMaxLoyalty, RebelCommand::GetMaxTownLoyalty(bTownId));
// check if we'd be going over the max
if( (gTownLoyalty[ bTownId ].ubRating + sRatingChange ) >= ubMaxLoyalty )
{
// set to max and null out gain pts
gTownLoyalty[ bTownId ].ubRating = ubMaxLoyalty;
gTownLoyalty[ bTownId ].sChange = 0;
}
else
{
// increment loyalty rating, reduce sChange
gTownLoyalty[ bTownId ].ubRating += sRatingChange;
gTownLoyalty[ bTownId ].sChange %= GAIN_PTS_PER_LOYALTY_PT;
}
}
else
// if loyalty is ready to decrease
if( sRatingChange < 0 )
{
// check if we'd be going below zero
if( ( gTownLoyalty[ bTownId ].ubRating + sRatingChange ) < 0 )
{
// set to zero and null out gain pts
gTownLoyalty[ bTownId ].ubRating = 0;
gTownLoyalty[ bTownId ].sChange = 0;
}
else
{
// decrement loyalty rating, reduce sChange
gTownLoyalty[ bTownId ].ubRating += sRatingChange;
gTownLoyalty[ bTownId ].sChange %= GAIN_PTS_PER_LOYALTY_PT;
}
}
// check old aginst new, if diff, dirty map panel
if( ubOldLoyaltyRating != gTownLoyalty[ bTownId ].ubRating )
{
fMapPanelDirty = TRUE;
}
}
// strategic handler, goes through and handles all strategic events for town loyalty updates...player controlled, monsters
void HandleTownLoyalty( void )
{
/* ARM: removed to experiment with keeping loyalty from drifing without any direct causes from the player
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
// update loyalty of controlled/uncontrolled towns
UpdateLoyaltyBasedOnControl( bTownId );
// update based on number of good guys in sector
UpdateTownLoyaltyBasedOnFriendliesInTown( bTownId );
// update bad on number of badies in sector
UpdateTownLoyaltyBasedOnBadGuysInTown( bTownId );
// finally update town loyalty
UpdateTownLoyaltyRating( bTownId );
}
*/
// now all loyalty is done, handle other stuff
/* ARM: Civilian theft of items was removed
// only check for theft every 12 hours, otherwise it's way too slow
if ( (GetWorldHour() % 12) == 0)
{
HandleTownTheft( );
}
*/
return;
}
/*
// update of town loyalty based on if the player controls the town's sectors
void UpdateLoyaltyBasedOnControl( INT8 bTownId )
{
// compare current loyalty to percent of sectors controlled, adjust in that direction
INT32 iUnderControl = 0;
Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
// find how much of town is under control
iUnderControl = GetTownSectorsUnderControl( bTownId );
iUnderControl *= 100;
iUnderControl /= GetTownSectorSize( bTownId );
// the gain value will be the difference in iUnderControl and the current Loyalty rating / hours_per_day
if( gTownLoyalty[ bTownId ].ubRating < ( UINT8 ) iUnderControl )
{
// we control more of the town, start moving to more loyal
IncrementTownLoyalty( bTownId, ( iUnderControl - gTownLoyalty[ bTownId ].ubRating) );
}
else
{
// mere lack of sector control never reduces loyalty below the town's current rebel sentiment
if (gTownLoyalty[ bTownId ].ubRating > gubTownRebelSentiment[ bTownId ])
{
// we control less of the town, decrement gain
DecrementTownLoyalty( bTownId, gTownLoyalty[ bTownId ].ubRating - iUnderControl );
}
}
return;
}
// updates the loyalty of the town in a sector based on number of friendly troops in that sector ...to a point, after max number to
void UpdateTownLoyaltyBasedOnFriendliesInTown( INT8 bTownId )
{
// check if valid town
INT32 iUnderControl = 0;
SOLDIERTYPE *pSoldier;
INT32 iCounter = 0;
INT32 iSoldierCount = 0;
INT32 iLocalNPCBonus = 0;
INT32 iMilitiaValue = 0;
UINT8 ubMilitiaLevel = 0;
Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
// find how much of town is under control
iUnderControl = GetTownSectorsUnderControl( bTownId );
iUnderControl *= 100;
iUnderControl /= GetTownSectorSize( bTownId );
// count how many of player's mercs are active in this town, then factor by number of sectors under control
// run through list of grunts on team
for (iCounter = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; iCounter <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; iCounter++ )
{
pSoldier = MercPtrs[ iCounter ];
if ( pSoldier->stats.bLife >= OKLIFE && pSoldier->bActive )
{
// if soldier is in this sector
if( SectorIsPartOfTown( bTownId, pSoldier->sSectorX, pSoldier->sSectorY ) == TRUE )
{
// if onduty or in a vehicle
if( ( pSoldier->bAssignment < ON_DUTY ) || pSoldier->bAssignment == VEHICLE ) )
{
// increment soldier count
iSoldierCount++;
}
// local influence: if the town is the character's home town
if( bTownId == gMercProfiles[ pSoldier->ubProfile ].bTown )
{
// he needn't be soldiering to have an impact... presence is enough
if( pSoldier->bAssignment < ASSIGNMENT_DEAD )
{
iLocalNPCBonus = HOURLY_GAIN_FOR_LOCAL_NPC_IN_TOWN;
iLocalNPCBonus *= gMercProfiles[ pSoldier->ubProfile ].bTownAttachment;
iLocalNPCBonus /= 100;
// adjust for % town control
iLocalNPCBonus *= iUnderControl;
iLocalNPCBonus /= 100;
// do we actually gain from this?
if( iLocalNPCBonus > 0 )
{
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"%s influences loyalty in home town of %s, worth %d", pSoldier->name, pTownNames[ bTownId ], iLocalNPCBonus);
// increment town loyalty
IncrementTownLoyalty( bTownId, iLocalNPCBonus );
}
}
}
}
}
}
// soldier count is limited by a max number
if( iSoldierCount > MAX_SOLDIER_COUNT_FOR_IN_TOWN_LOYALTY_GAIN )
{
iSoldierCount = MAX_SOLDIER_COUNT_FOR_IN_TOWN_LOYALTY_GAIN;
}
// adjust for % town control
iSoldierCount *= iUnderControl;
iSoldierCount /= 100;
// add loyalty gain due to mercs
IncrementTownLoyalty( bTownId, iSoldierCount * HOURLY_GAIN_PER_MERC_IN_TOWN );
// find out if there are any militia anywhere in this town
iMilitiaValue = 0;
for( ubMilitiaLevel = 0; ubMilitiaLevel < MAX_MILITIA_LEVELS; ubMilitiaLevel++ )
{
// regulars/elites are worth double/triple normal
iMilitiaValue += ((ubMilitiaLevel + 1) * GetMilitiaCountAtLevelAnywhereInTown( bTownId, ubMilitiaLevel ));
}
// militia count is limited by a max number
if( iMilitiaValue > MAX_MILITIA_VALUE_FOR_IN_TOWN_LOYALTY_GAIN )
{
iMilitiaValue = MAX_MILITIA_VALUE_FOR_IN_TOWN_LOYALTY_GAIN;
}
// adjust for % town control
iMilitiaValue *= iUnderControl;
iMilitiaValue /= 100;
// add loyalty gain due to militia
IncrementTownLoyalty( bTownId, iMilitiaValue * HOURLY_GAIN_PER_MILITIA_IN_TOWN );
return;
}
void UpdateTownLoyaltyBasedOnBadGuysInTown( INT8 bTownId )
{
// will update a town's loyalty based on number and type of bad guys in it
INT32 iTotalEnemies = 0;
INT16 sSectorX =0, sSectorY = 0;
UINT8 ubAdmins, ubRegs, ubElites;
INT32 iUnderControl;
// find which sectors are this town and look at bad guys in those sectors
// adjust for number of sectors bad guys control...like the player
for( sSectorX = 0; sSectorX < MAP_WORLD_X; sSectorX++ )
{
for( sSectorY = 0; sSectorY < MAP_WORLD_Y; sSectorY++ )
{
// check if sector belongs to this town and is controlled by bad guys
if ( ( SectorIsPartOfTown( bTownId, sSectorX, sSectorY ) == TRUE ) && ( StrategicMap[ sSectorX + sSectorY * MAP_WORLD_X ].fEnemyControlled == TRUE ) )
{
// controlled by bad guys..adjust numbers for type
GetNumberOfEnemiesInSector( sSectorX, sSectorY, &ubAdmins, &ubRegs, &ubElites );
// administrators
iTotalEnemies += ( INT32 ) ubAdmins;
// regulars
iTotalEnemies += ( INT32 ) ubRegs * NUMBER_OF_RANKS_PER_REGULAR;
// elites
iTotalEnemies += ( INT32 ) ubElites * NUMBER_OF_RANKS_PER_ELITE;
}
}
}
// check vs. maximum influence possible
if( iTotalEnemies > MAX_ENEMY_SOLDIER_RANKS_FOR_IN_TOWN_LOYALTY_DROP )
{
// we have, adjust
iTotalEnemies = MAX_ENEMY_SOLDIER_RANKS_FOR_IN_TOWN_LOYALTY_DROP;
}
// find how much of town is under enemy control
iUnderControl = GetTownSectorSize( bTownId ) - GetTownSectorsUnderControl( bTownId );
iUnderControl *= 100;
iUnderControl /= GetTownSectorSize( bTownId );
// adjust number of enemies for town control
iTotalEnemies *= iUnderControl;
iTotalEnemies /= 100;
// adjust loyalty downward for enemies
DecrementTownLoyalty( bTownId, iTotalEnemies * HOURLY_DROP_PER_ENEMY_RANK_IN_TOWN );
}
*/
void HandleMurderOfCivilian( SOLDIERTYPE *pSoldier, BOOLEAN fIntentional )
{
// handle the impact on loyalty of the murder of a civilian
INT8 bTownId = 0;
INT32 iLoyaltyChange = 0;
INT8 bSeenState = 0;
SOLDIERTYPE *pCivSoldier = NULL;
UINT32 uiChanceFalseAccusal = 0;
INT8 bKillerTeam = 0;
BOOLEAN fIncrement = FALSE;
// ubAttacker CAN be NOBODY... Don't treat is as murder if NOBODY killed us...
if ( pSoldier->ubAttackerID == NOBODY )
{
return;
}
// ignore murder of non-civilians!
if ( ( pSoldier->bTeam != CIV_TEAM ) || ( pSoldier->ubBodyType == CROW ) )
{
return;
}
// if this is a profiled civilian NPC
if ( pSoldier->ubProfile != NO_PROFILE )
{
// ignore murder of NPCs if they're not loyal to the rebel cause - they're really just enemies in civilian clothing
if ( gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 & PROFILE_MISC_FLAG3_TOWN_DOESNT_CARE_ABOUT_DEATH )
{
return;
}
}
// Flugente: if a civilian's group has Loyalty set to 0, there will be no morale penalty/loyalty change if he is killed. Pretty odd, but at least not as bugged as it was before.
// if civilian belongs to a civilian group
if ( pSoldier->ubCivilianGroup != NON_CIV_GROUP && pSoldier->ubCivilianGroup < NUM_CIV_GROUPS )
{
if ( !zCivGroupName[pSoldier->ubCivilianGroup].Loyalty )
return;
}
/*
// if civilian belongs to a civilian group
if ( pSoldier->ubCivilianGroup != NON_CIV_GROUP )
{
// and it's one that is hostile to the player's cause
switch ( pSoldier->ubCivilianGroup )
{
case KINGPIN_CIV_GROUP:
case ALMA_MILITARY_CIV_GROUP:
case HICKS_CIV_GROUP:
case WARDEN_CIV_GROUP:
return;
}
//New Group by Jazz
for( iCounter2 = UNNAMED_CIV_GROUP_15; iCounter2 < NUM_CIV_GROUPS; iCounter2++ )
{
if (pSoldier->ubCivilianGroup == iCounter2)
return;
}
}
*/
// set killer team
bKillerTeam = pSoldier->ubAttackerID->bTeam;
// if the player did the killing
if( bKillerTeam == OUR_TEAM )
{
SOLDIERTYPE *pKiller = pSoldier->ubAttackerID;
// apply morale penalty for killing a civilian!
HandleMoraleEvent( pKiller, MORALE_KILLED_CIVILIAN, pKiller->sSectorX, pKiller->sSectorY, pKiller->bSectorZ );
}
// get town id
bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY );
// if civilian is NOT in a town
if( bTownId == BLANK_SECTOR )
{
return;
}
// check if this town does use loyalty (to skip a lot of unnecessary computation)
if (!gfTownUsesLoyalty[ bTownId ])
{
return;
}
if( ( pSoldier->ubBodyType >= FATCIV) && ( pSoldier->ubBodyType <= COW ) )
{
// adjust value for killer and type
iLoyaltyChange = uiPercentLoyaltyDecreaseForCivMurder[ pSoldier->ubBodyType - FATCIV ];
}
else
{
iLoyaltyChange = BASIC_COST_FOR_CIV_MURDER;
}
if( !fIntentional )
{
// accidental killing, reduce value
iLoyaltyChange *= REDUCTION_FOR_UNINTENTIONAL_KILLING;
iLoyaltyChange /= 100;
}
// check if LOS between any civ, killer and killed
// if so, then do not adjust
for( SoldierID iCounter = gTacticalStatus.Team[ CIV_TEAM ].bFirstID; iCounter <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; ++iCounter )
{
// set current civ soldier
pCivSoldier = iCounter;
if ( pCivSoldier == pSoldier )
{
continue;
}
// killer seen by civ?
if ( SoldierToSoldierLineOfSightTest( pCivSoldier, pSoldier->ubAttackerID, TRUE, gGameExternalOptions.ubStraightSightRange ) != 0 )
{
bSeenState |= 1;
}
// victim seen by civ?
if( SoldierToSoldierLineOfSightTest( pCivSoldier, pSoldier, TRUE, gGameExternalOptions.ubStraightSightRange ) != 0 )
{
bSeenState |= 2;
}
}
// if player didn't do it
if( bKillerTeam != OUR_TEAM )
{
// If the murder is not fully witnessed, there's a chance of player being blamed for it even if it's not his fault
switch (bSeenState)
{
case 0:
// nobody saw anything. When body is found, chance player is blamed depends on the town's loyalty at this time
uiChanceFalseAccusal = MAX_LOYALTY_VALUE - gTownLoyalty[ bTownId ].ubRating;
break;
case 1:
// civilians saw the killer, but not the victim. 10 % chance of blaming player whether or not he did it
uiChanceFalseAccusal = 10;
break;
case 2:
// civilians only saw the victim. 50/50 chance...
uiChanceFalseAccusal = 50;
break;
case 3:
// civilians have seen it all, and in this case, they're always honest
uiChanceFalseAccusal = 0;
break;
default:
Assert(FALSE);
return;
}
// check whether player is falsely accused
if( Random(100) < uiChanceFalseAccusal )
{
// blame player whether or not he did it - set killer team as our team
bKillerTeam = OUR_TEAM;
// not really player's fault, reduce penalty, because some will believe it to be a lie
iLoyaltyChange *= REDUCTION_FOR_MURDER_NOT_OUR_FAULT;
iLoyaltyChange /= 100;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"You're being blamed for a death you didn't cause!");
}
}
// check who town thinks killed the civ
switch (bKillerTeam)
{
case OUR_TEAM:
// town thinks player committed the murder, bad bad bad
fIncrement = FALSE;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"Civilian killed by friendly forces.");
break;
case ENEMY_TEAM:
// check whose sector this is
if( StrategicMap[( pSoldier->sSectorX ) + ( MAP_WORLD_X * ( pSoldier->sSectorY ) )].fEnemyControlled == TRUE )
{
// enemy soldiers... in enemy controlled sector. Gain loyalty
fIncrement = TRUE;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"Enemy soldiers murdered a civilian. Town loyalty increases");
}
else
{
// reduce, we're expected to provide some protection, but not miracles
iLoyaltyChange *= REDUCTION_FOR_MURDER_OF_INNOCENT_BY_ENEMY_IN_OUR_SECTOR;
iLoyaltyChange /= 100;
// lose loyalty
fIncrement = FALSE;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"Town holds you responsible for murder by enemy.");
}
break;
case MILITIA_TEAM:
// the rebels did it... check if are they on our side
if( CheckFact( FACT_REBELS_HATE_PLAYER, 0 ) == FALSE )
{
// on our side, penalty
iLoyaltyChange *= REDUCTION_FOR_MURDER_BY_REBEL;
iLoyaltyChange /= 100;
// lose loyalty
fIncrement = FALSE;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"Town holds you responsible for murder by rebels.");
}
break;
case CREATURE_TEAM:
// killed by a monster - make sure it was one
if( ( pSoldier->ubAttackerID->ubBodyType >= ADULTFEMALEMONSTER ) && ( pSoldier->ubAttackerID->ubBodyType <= QUEENMONSTER ) )
{
// increase for the extreme horror of being killed by a monster
iLoyaltyChange *= MULTIPLIER_FOR_MURDER_BY_MONSTER;
// check whose sector this is
if( StrategicMap[CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY )].fEnemyControlled == TRUE )
{
// enemy controlled sector - gain loyalty
fIncrement = TRUE;
}
else
{
// our sector - lose loyalty
fIncrement = FALSE;
}
}
break;
// Flugente: civilians might also kill somebody, no matter how unlikely that sounds
// punish the player - he didnt stop the bloodshed
case CIV_TEAM:
{
// on our side, penalty
iLoyaltyChange *= REDUCTION_FOR_MURDER_NOT_OUR_FAULT;
iLoyaltyChange /= 100;
// lose loyalty
fIncrement = FALSE;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"Town holds you responsible for not stopping the murder.");
}
break;
default:
Assert(FALSE);
return;
}
/* Delayed loyalty effects elimininated. Sep.12/98. ARM
// figure out how long before the news of the murder spreads and lowers town loyalty
if (bSeenState == 0)
{
// nobody saw nothing. Will be some time before the grisly remains are discovered...
uiLoyaltyEffectDelay = 60 * (1 + Random(12)); // 1-12 hrs in minutes
}
else
{
// there are witnesses, news reported & spread very quickly
uiLoyaltyEffectDelay = 30;
}
// create the event value, with bTownId & for iLoyaltyChange as data
uiValue = BuildLoyaltyEventValue( bTownId , iLoyaltyChange, fIncrement );
// post the event
AddStrategicEvent( EVENT_TOWN_LOYALTY_UPDATE, GetWorldTotalMin() + uiLoyaltyEffectDelay, uiValue );
// ideally, we'd like to also call AffectAllTownsLoyaltyByDistanceFrom() here to spread the news to all other towns,
// but we don't have that available on a delay right now, and it would be a bit of a pain, since we only have 32 bits
// of event data to pass in, and that would have to store iLoyaltyChange, sSectorX, and sSectorY (64 bits)
*/
// if it's a decrement, negate the value
if ( !fIncrement )
{
iLoyaltyChange *= -1;
}
// this is a hack: to avoid having to adjust the values, divide by 1.75 to compensate for the distance 0
iLoyaltyChange *= 100;
iLoyaltyChange /= (100 + ( 25 * LOYALTY_EVENT_DISTANCE_THRESHOLD ) );
AffectAllTownsLoyaltyByDistanceFrom( iLoyaltyChange, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
}
// check town and raise loyalty value for hiring a merc from a town...not a lot of a gain, but some
void HandleTownLoyaltyForNPCRecruitment( SOLDIERTYPE *pSoldier )
{
INT8 bTownId = 0;
UINT32 uiLoyaltyValue = 0;
// get town id civilian
bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY );
// is the merc currently in their home town?
if( bTownId == gMercProfiles[ pSoldier->ubProfile ].bTown )
{
// yep, value of loyalty bonus depends on his importance to this to town
uiLoyaltyValue = MULTIPLIER_LOCAL_RPC_HIRED * gMercProfiles[ pSoldier->ubProfile ].bTownAttachment;
// increment town loyalty gain
IncrementTownLoyalty( bTownId, uiLoyaltyValue );
// DESIGN QUESTION: How easy is it to abuse this bonus by repeatedly hiring the guy over & over
// (at worst daily? even more often if terminated & rehired?) (ARM)
}
return;
}
BOOLEAN HandleLoyaltyAdjustmentForRobbery( SOLDIERTYPE *pSoldier )
{
// not to be implemented at this time
return( FALSE );
/*
// this function will handle robbery by the passed soldiertype of an object from a town he/she is in
// it will check LOS code for all civilians around the stealing soldier if any of them have LOS to the soldier
// then roll against thief's dexterity, if fail then you were caught( adjust loyalty base on theft and value of the object and if the object is owned by one of the people with LOS) and return a TRUE
// otherwise return false.
INT8 bTownId = 0;
// get town id
bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY );
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"Theft not yet implemented.");
return ( FALSE );
*/
}
// handle loyalty adjustment for dmg inflicted on a building
void HandleLoyaltyForDemolitionOfBuilding( SOLDIERTYPE *pSoldier, INT16 sPointsDmg )
{
// find this soldier's team and decrement the loyalty rating for them and for the people who police the sector
// more penalty for the people who did it, a lesser one for those who should have stopped it
INT16 sLoyaltyValue = 0;
INT16 sPolicingLoyalty = 0;
INT8 bTownId = 0;
// hurt loyalty for damaging the building
sLoyaltyValue = sPointsDmg * MULTIPLIER_FOR_DAMAGING_A_BUILDING;
// penalty for not preventing the action
sPolicingLoyalty = sPointsDmg * MULTIPLIER_FOR_NOT_PREVENTING_BUILDING_DAMAGE;
// get town id
bTownId = GetTownIdForSector( pSoldier->sSectorX, pSoldier->sSectorY );
// Flugente: for safety reasons, exit if not in a town
if( bTownId == BLANK_SECTOR )
return;
// penalize the side that did it
if( pSoldier->bTeam == OUR_TEAM )
{
DecrementTownLoyalty( bTownId, sLoyaltyValue );
}
else if( pSoldier->bTeam == ENEMY_TEAM )
{
// enemy damaged sector, it's their fault
IncrementTownLoyalty( bTownId, sLoyaltyValue );
}
else if ( pSoldier->ubCivilianGroup == REBEL_CIV_GROUP )
{
// the rebels did it...are they on our side
if( CheckFact( FACT_REBELS_HATE_PLAYER, 0 ) == FALSE )
{
sLoyaltyValue /= DIVISOR_FOR_REBEL_BUILDING_DMG;
// decrement loyalty value for rebels on our side dmging town
DecrementTownLoyalty( bTownId, sLoyaltyValue );
}
}
// penalize the side that should have stopped it
if( StrategicMap[CALCULATE_STRATEGIC_INDEX( pSoldier->sSectorX, pSoldier->sSectorY )].fEnemyControlled == TRUE )
{
// enemy should have prevented it, let them suffer a little
IncrementTownLoyalty( bTownId, sPolicingLoyalty );
}
else
{
// we should have prevented it, we shall suffer
DecrementTownLoyalty( bTownId, sPolicingLoyalty );
}
return;
}
static BOOLEAN CanRemoveWorldItem( WORLDITEM *worlditem, UINT32 uiMaxWeight, UINT32 *pStackWeight )
{
BOOLEAN result = FALSE;
UINT32 uiChance = 10UL * Item[worlditem->object.usItem].ubCoolness;
UINT32 uiStackWeight = 0;
for ( int objIndex = 0; objIndex < worlditem->object.ubNumberOfObjects; objIndex++ )
uiStackWeight += worlditem->object.GetWeightOfObjectInStack( objIndex );
if ( Chance( uiChance ) && uiStackWeight <= uiMaxWeight )
result = TRUE;
*pStackWeight = uiStackWeight;
return result;
}
void RemoveRandomItemsInSector( INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ, UINT32 uiNumEnemies )
{
const UINT32 uiCarryWeightPerSoldier = 30; // 3 kg
// remove random items in sector
UINT32 uiNumberOfItems = 0, iCounter = 0, uiStackWeight = 0;
UINT32 uiWeightToSteal = uiCarryWeightPerSoldier * uiNumEnemies;
std::vector<WORLDITEM> pItemList;//dnl ch75 271013
UINT32 uiNewTotal = 0;
CHAR16 wSectorName[ 128 ];
if(gGameExternalOptions.fNoRemoveRandomSectorItems)//dnl ch68 090913
return;
// stealing should fail anyway 'cause there shouldn't be a temp file for unvisited sectors, but let's check anyway
Assert( GetSectorFlagStatus( sSectorX, sSectorY, ( UINT8 ) sSectorZ, SF_ALREADY_VISITED ) == TRUE );
// get sector name string
GetSectorIDString( sSectorX, sSectorY, ( INT8 ) sSectorZ, wSectorName, TRUE );
// go through list of items in sector and randomly remove them
// if unloaded sector
if( gWorldSectorX != sSectorX || gWorldSectorY != sSectorY || gbWorldSectorZ != sSectorZ )
{
// if the player has never been there, there's no temp file, and 0 items will get returned, preventing any stealing
const auto ii = FindWorldItemSector(sSectorX, sSectorY, (UINT8)sSectorZ);
if (ii != -1)
{
uiNumberOfItems = gAllWorldItems.NumItems[ii];
pItemList = gAllWorldItems.Items[ii];
}
if( uiNumberOfItems == 0 )
{
return;
}
uiNewTotal = uiNumberOfItems;
// set up item list ptrs
for( iCounter = 0; iCounter < uiNumberOfItems ; ++iCounter )
{
//if the item exists, and is visible and reachable, see if it should be stolen
if ( pItemList[ iCounter ].fExists && pItemList[ iCounter ].bVisible == TRUE && pItemList[ iCounter ].usFlags & WORLD_ITEM_REACHABLE )
{
if ( CanRemoveWorldItem( &pItemList[iCounter], uiWeightToSteal, &uiStackWeight ) )
{
// remove
--uiNewTotal;
pItemList[ iCounter ].fExists = FALSE;
uiWeightToSteal -= uiStackWeight;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"%s stolen in %s!", ItemNames[ pItemList[ iCounter ].object.usItem ], wSectorName );
}
}
}
// only save if something was stolen
if (uiNewTotal < uiNumberOfItems)
{
AddWorldItemsToUnLoadedSector( sSectorX, sSectorY, ( UINT8 )sSectorZ, 0, uiNumberOfItems, pItemList, TRUE );
}
}
else // handle a loaded sector
{
for( iCounter = 0; iCounter < guiNumWorldItems; ++iCounter )
{
// note, can't do reachable test here because we'd have to do a path call...
if ( gWorldItems[ iCounter ].fExists && gWorldItems[ iCounter ].bVisible == TRUE )
{
if ( CanRemoveWorldItem( &gWorldItems[iCounter], uiWeightToSteal, &uiStackWeight ) )
{
RemoveItemFromPool( gWorldItems[ iCounter ].sGridNo , iCounter, gWorldItems[ iCounter ].ubLevel );
uiWeightToSteal -= uiStackWeight;
// debug message
ScreenMsg( MSG_FONT_RED, MSG_DEBUG, L"%s stolen in %s!", ItemNames[ gWorldItems[ iCounter ].object.usItem ], wSectorName );
}
}
}
}
}
/* ARM: Civilian theft of items was removed
void HandleTheftByCiviliansInSector( INT16 sX, INT16 sY, INT32 iLoyalty )
{
UINT8 ubChance = 0;
// any loyalty under the theft threshhold is chance that thefts will occur
if( iLoyalty < THRESHOLD_FOR_TOWN_THEFT )
{
// sectors with mercs are immune
if( fSectorsWithSoldiers[ sX + ( MAP_WORLD_X * sY ) ][ 0 ] == FALSE )
{
// sectors not yet visited by player are also immune (other sectors in town could have been visited)
if ( GetSectorFlagStatus( sX, sY, 0, SF_ALREADY_VISITED ) == TRUE )
{
ubChance = 5 + ( THRESHOLD_FOR_TOWN_THEFT - iLoyalty ) / 2;
// remove items from sector
RemoveRandomItemsInSector( sX, sY, 0, ubChance);
}
}
}
return;
}
void HandleTownTheft( void )
{
INT32 iCounter = 0;
UINT8 ubTown;
// init sectors with soldiers list
InitSectorsWithSoldiersList( );
// build list
BuildSectorsWithSoldiersList( );
while( pTownNamesList[ iCounter ] != 0 )
{
ubTown = StrategicMap[ pTownLocationsList[ iCounter ] ].bNameId;
// if this town tracks loyalty, and tracking has started
if ( ( ubTown != BLANK_SECTOR ) && gfTownUsesLoyalty[ ubTown ] && gTownLoyalty[ ubTown ].fStarted )
{
// handle theft in this town sector
HandleTheftByCiviliansInSector( ( INT16 )( pTownLocationsList[ iCounter ] % MAP_WORLD_X ), ( INT16 )( pTownLocationsList[ iCounter ] / MAP_WORLD_X ), gTownLoyalty[ ubTown ].ubRating );
}
++iCounter;
}
}
*/
void BuildListOfTownSectors( void )
{
INT32 iCounterX = 0, iCounterY = 0, iCounter = 0;
UINT16 usSector;
// initialize
memset( pTownNamesList, BLANK_SECTOR, sizeof( pTownNamesList ) );
memset( pTownLocationsList, 0xFF, sizeof( pTownLocationsList ) );
// run through list
for( iCounterX = 0; iCounterX < MAP_WORLD_X; iCounterX++ )
{
for( iCounterY = 0; iCounterY < MAP_WORLD_Y; iCounterY++ )
{
usSector = CALCULATE_STRATEGIC_INDEX( iCounterX, iCounterY );
if( ( StrategicMap[ usSector ].bNameId >= FIRST_TOWN ) && ( StrategicMap[ usSector ].bNameId < NUM_TOWNS ) )
{
pTownNamesList[ iCounter] = StrategicMap[ usSector ].bNameId;
pTownLocationsList[ iCounter ] = usSector;
iCounter++;
}
}
}
}
//#ifdef JA2TESTVERSION
void CalcDistancesBetweenTowns( void )
{
// run though each town sector and compare it to the next in terms of distance
UINT8 ubTownA, ubTownB;
UINT32 uiCounterA, uiCounterB;
UINT8 ubTempGroupId = 0;
INT32 iDistance = 0;
// this is a little bit tricky, since towns can have multiple sectors, we have to measure all possible combinations
// preset all distances between towns to high values to prepare for search for the minimum distance
for( ubTownA = FIRST_TOWN; ubTownA < NUM_TOWNS; ubTownA++ )
{
for( ubTownB = FIRST_TOWN; ubTownB < NUM_TOWNS; ubTownB++ )
{
iTownDistances[ ubTownA ][ ubTownB ] = 999999;
}
}
// create a temporary group (needed to plot strategic paths)
ubTempGroupId = CreateNewPlayerGroupDepartingFromSector( 1, 1 );
// now, measure distance from every town sector to every other town sector!
// The minimum distances between towns get stored.
uiCounterA = 0;
uiCounterB = 0;
while( pTownNamesList[ uiCounterA ] != 0 )
{
// reset second counter
uiCounterB = uiCounterA;
while( pTownNamesList[ uiCounterB ] != 0)
{
ubTownA = (UINT8) pTownNamesList[ uiCounterA ];
ubTownB = (UINT8) pTownNamesList[ uiCounterB ];
// if they're not in the same town
if (ubTownA != ubTownB)
{
// calculate fastest distance between them (in sectors) - not necessarily shortest distance, roads are faster!
iDistance = FindStratPath( ( INT16 )pTownLocationsList[uiCounterA ], ( INT16 )pTownLocationsList[ uiCounterB ], ubTempGroupId, FALSE );
}
else
{
// same town, distance is 0 by definition
iDistance = 0;
}
// if it's the fastest route so far, store its length
if( iDistance < iTownDistances[ ubTownA ][ ubTownB ] )
{
// store it going both ways! The inner while loop is optimized to search each pair only once.
iTownDistances[ ubTownA ][ ubTownB ] = iDistance;
iTownDistances[ ubTownB ][ ubTownA ] = iDistance;
}
uiCounterB++;
}
uiCounterA++;
}
RemoveGroup( ubTempGroupId );
}
void WriteOutDistancesBetweenTowns( void )
{
HWFILE hFileHandle;
hFileHandle = FileOpen( "BinaryData\\TownDistances.dat", FILE_ACCESS_WRITE|FILE_OPEN_ALWAYS, FALSE );
FileWrite( hFileHandle, &( iTownDistances ), ( sizeof( INT32 ) * MAX_TOWNS * MAX_TOWNS ), NULL );
// close file
FileClose( hFileHandle );
return;
}
void DumpDistancesBetweenTowns(void)
{
CHAR8 zPrintFileName[60];
FILE *FDump;
UINT8 ubTownA, ubTownB;
CHAR16 wHeading[4];
// open output file
strcpy(zPrintFileName, "TownDistances.txt");
FDump = fopen(zPrintFileName, "wt");
if (FDump == NULL)
{
return;
}
// print headings
fprintf(FDump, " ");
for( ubTownB = FIRST_TOWN; ubTownB < NUM_TOWNS; ubTownB++ )
{
wcsncpy(wHeading, pTownNames[ubTownB], 3);
wHeading[3] = '\0';
fprintf(FDump, " %ls", wHeading);
}
fprintf(FDump, "\n");
fprintf(FDump, " ");
for( ubTownB = FIRST_TOWN; ubTownB < NUM_TOWNS; ubTownB++ )
{
fprintf(FDump, " ---");
}
fprintf(FDump, "\n");
for( ubTownA = FIRST_TOWN; ubTownA < NUM_TOWNS; ubTownA++ )
{
fprintf(FDump, "%12ls", pTownNames[ubTownA]);
for( ubTownB = FIRST_TOWN; ubTownB < NUM_TOWNS; ubTownB++ )
{
fprintf(FDump, " %3d", iTownDistances[ ubTownA ][ ubTownB ]);
}
fprintf(FDump, "\n");
}
fclose(FDump);
}
//#endif
void ReadInDistancesBetweenTowns( void )
{
HWFILE hFileHandle;
hFileHandle = FileOpen( "BinaryData\\TownDistances.dat", FILE_ACCESS_READ, FALSE );
FileRead( hFileHandle, &( iTownDistances ), ( sizeof( INT32 ) * MAX_TOWNS * MAX_TOWNS ), NULL );
// close file
FileClose( hFileHandle );
return;
}
INT32 GetTownDistances( UINT8 ubTown, UINT8 ubTownA )
{
return( iTownDistances[ ubTown ][ ubTownA ] );
}
/* Delayed loyalty effects elimininated. Sep.12/98. ARM
void HandleDelayedTownLoyaltyEvent( UINT32 uiValue )
{
INT8 bTownId = 0;
UINT32 uiLoyaltyChange = 0;
BOOLEAN fIncrement = FALSE;
// first 8 bits are town id, 23 are amount and last is increment or decrement
// get increment
fIncrement = ( BOOLEAN )( GET_TOWN_INCREMENT( iValue ) ) ;
// town id
bTownId = ( INT8 )( GET_TOWN_ID_FOR_LOYALTY( iValue ) );
// the amount
uiLoyaltyChange = ( INT32 )( GET_TOWN_LOYALTY_CHANGE( iValue ) );
if (fIncrement)
{
IncrementTownLoyalty( bTownId, uiLoyaltyChange );
}
else
{
DecrementTownLoyalty( bTownId, uiLoyaltyChange );
}
}
UINT32 BuildLoyaltyEventValue( INT8 bTownValue, UINT32 uiValue, BOOLEAN fIncrement )
{
UINT32 uiReturnValue = 0;
UINT32 uiTempValue = 0;
// the town name in 8 most sig bits
uiTempValue = ( UINT32 )( bTownValue );
uiReturnValue += ( uiTempValue << 24 );
// the incrment decrement amount is
uiTempValue = ( UINT32 )( uiValue );
uiTempValue = ( ( uiTempValue << 8 ) >> 7 );
uiReturnValue += uiTempValue;
uiReturnValue += ( fIncrement != 0 ? 1: 0 );
return( uiReturnValue );
}
*/
BOOLEAN SaveStrategicTownLoyaltyToSaveGameFile( HWFILE hFile )
{
UINT32 uiNumBytesWritten;
//Save the Town Loyalty
FileWrite( hFile, gTownLoyalty, sizeof( TOWN_LOYALTY ) * MAX_TOWNS, &uiNumBytesWritten );
if( uiNumBytesWritten != sizeof( TOWN_LOYALTY ) * MAX_TOWNS )
{
return( FALSE );
}
return( TRUE );
}
BOOLEAN LoadStrategicTownLoyaltyFromSavedGameFile( HWFILE hFile )
{
UINT32 uiNumBytesRead;
//Restore the Town Loyalty
FileRead( hFile, gTownLoyalty, sizeof( TOWN_LOYALTY ) * MAX_TOWNS, &uiNumBytesRead );
if( uiNumBytesRead != sizeof( TOWN_LOYALTY ) * MAX_TOWNS )
{
return( FALSE );
}
return( TRUE );
}
void ReduceLoyaltyForRebelsBetrayed(void)
{
INT8 bTownId;
// reduce loyalty to player all across Arulco
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
if (bTownId == OMERTA)
{
// if not already really low
if (gTownLoyalty[ bTownId ].ubRating > HOSTILE_OMERTA_LOYALTY_RATING)
{
// loyalty in Omerta tanks big time, and will stay low permanently since this becomes its maximum
SetTownLoyalty(bTownId, HOSTILE_OMERTA_LOYALTY_RATING);
// note that rebel sentiment isn't affected - they remain loyal to themselves, after all!
}
}
else
{
// loyalty in other places is also strongly affected by this falling out with rebels, but this is not permanent
SetTownLoyalty(bTownId, (UINT8) (gTownLoyalty[ bTownId ].ubRating / 3));
}
}
}
INT32 GetNumberOfWholeTownsUnderControl( void )
{
INT32 iNumber = 0;
INT8 bTownId = 0;
// run through the list of towns..if the entire town is under player control, then increment the number of towns under player control
// make sure that each town is one for which loyalty matters
for ( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
if ( IsTownUnderCompleteControlByPlayer( bTownId ) && gfTownUsesLoyalty[ bTownId ] )
{
iNumber++;
}
}
return( iNumber );
}
INT32 GetNumberOfWholeTownsUnderControlButExcludeCity( INT8 bCityToExclude )
{
INT32 iNumber = 0;
INT8 bTownId = 0;
// run through the list of towns..if the entire town is under player control, then increment the number of towns under player control
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
if ( IsTownUnderCompleteControlByPlayer( bTownId ) && ( bCityToExclude != bTownId ) && gfTownUsesLoyalty[ bTownId ] )
{
iNumber++;
}
}
return( iNumber );
}
// is the ENTIRE town under player control?
INT32 IsTownUnderCompleteControlByPlayer( INT8 bTownId )
{
if( GetTownSectorSize( bTownId ) == GetTownSectorsUnderControl( bTownId ) )
{
return( TRUE );
}
return( FALSE );
}
// is the ENTIRE town under enemy control?
INT32 IsTownUnderCompleteControlByEnemy( INT8 bTownId )
{
if ( GetTownSectorsUnderControl( bTownId ) == 0 )
{
return( TRUE );
}
return( FALSE );
}
void AdjustLoyaltyForCivsEatenByMonsters( INT16 sSectorX, INT16 sSectorY, UINT8 ubHowMany, BOOLEAN aBandits)
{
INT8 bTownId = 0;
UINT32 uiLoyaltyChange = 0;
CHAR16 str[256];
CHAR16 pSectorString[128];
// get town id
bTownId = GetTownIdForSector( sSectorX, sSectorY );
// if NOT in a town
if( bTownId == BLANK_SECTOR )
{
return;
}
//Report this to player
GetSectorIDString( sSectorX, sSectorY, 0, pSectorString, TRUE );
if ( aBandits )
swprintf( str, gpStrategicString[STR_PB_BANDIT_KILLCIVS_IN_SECTOR], ubHowMany, pSectorString );
else
{
if( g_lang == i18n::Lang::zh ) {
//diffrent order of words in Chinese
swprintf( str, gpStrategicString[STR_DIALOG_CREATURES_KILL_CIVILIANS], pSectorString, ubHowMany );
} else {
swprintf( str, gpStrategicString[STR_DIALOG_CREATURES_KILL_CIVILIANS], ubHowMany, pSectorString );
}
}
DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, MapScreenDefaultOkBoxCallback );
// use same formula as if it were a civilian "murder" in tactical!!!
uiLoyaltyChange = ubHowMany * BASIC_COST_FOR_CIV_MURDER * MULTIPLIER_FOR_MURDER_BY_MONSTER;
DecrementTownLoyalty( bTownId, uiLoyaltyChange );
}
// this applies the SAME change to every town equally, regardless of distance from the event
void IncrementTownLoyaltyEverywhere( UINT32 uiLoyaltyIncrease )
{
INT8 bTownId;
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
IncrementTownLoyalty( bTownId, uiLoyaltyIncrease );
}
}
void DecrementTownLoyaltyEverywhere( UINT32 uiLoyaltyDecrease )
{
INT8 bTownId;
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
DecrementTownLoyalty( bTownId, uiLoyaltyDecrease );
}
}
// this applies the change to every town differently, depending on the distance from the event
void HandleGlobalLoyaltyEvent( UINT8 ubEventType, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ)
{
INT8 bTownId = 0;
if( bSectorZ == 0 )
{
// grab town id, if this event occured within one
bTownId = GetTownIdForSector( sSectorX, sSectorY );
}
// should other towns ignore events occuring in this town?
if( bTownId == SAN_MONA )
{
return;
}
//move to lua scripts StrategicTownLoyalty.lua
#ifdef LUA_STRATEGY_TOWN_LOYALTY
LuaHandleGlobalLoyaltyEvent( ubEventType, sSectorX, sSectorY, bSectorZ, 0 );
#else
INT32 iLoyaltyChange;
// determine what the base loyalty change of this event type is worth
// these are->hundredths<- of loyalty points, so choose appropriate values accordingly!
// the closer a town is to the event, the more pronounced the effect upon that town is
switch (ubEventType)
{
case GLOBAL_LOYALTY_BATTLE_WON:
CheckConditionsForTriggeringCreatureQuest( sSectorX, sSectorY, bSectorZ );
iLoyaltyChange = 500;
break;
case GLOBAL_LOYALTY_QUEEN_BATTLE_WON:
CheckConditionsForTriggeringCreatureQuest( sSectorX, sSectorY, bSectorZ );
iLoyaltyChange = 1000;
break;
case GLOBAL_LOYALTY_BATTLE_LOST:
iLoyaltyChange = -750;
break;
case GLOBAL_LOYALTY_ENEMY_KILLED:
iLoyaltyChange = 50;
break;
case GLOBAL_LOYALTY_NATIVE_KILLED:
// note that there is special code (much more severe) for murdering civilians in the currently loaded sector.
// this event is intended more for processing militia casualties, and the like
iLoyaltyChange = -50;
break;
case GLOBAL_LOYALTY_ABANDON_MILITIA:
// it doesn't matter how many of them are being abandoned
iLoyaltyChange = -750;
break;
case GLOBAL_LOYALTY_GAIN_TOWN_SECTOR:
iLoyaltyChange = 500;
break;
case GLOBAL_LOYALTY_LOSE_TOWN_SECTOR:
iLoyaltyChange = -1000;
break;
case GLOBAL_LOYALTY_LIBERATE_WHOLE_TOWN:
iLoyaltyChange = 1000;
break;
case GLOBAL_LOYALTY_GAIN_MINE:
iLoyaltyChange = 1000;
break;
case GLOBAL_LOYALTY_LOSE_MINE:
iLoyaltyChange = -1500;
break;
case GLOBAL_LOYALTY_GAIN_SAM:
iLoyaltyChange = 250;
break;
case GLOBAL_LOYALTY_LOSE_SAM:
iLoyaltyChange = -250;
break;
// Flugente: morale drop for torturing prisoners
case GLOBAL_LOYALTY_PRISONERS_TORTURED:
iLoyaltyChange = -25;
break;
default:
Assert(FALSE);
return;
}
AffectAllTownsLoyaltyByDistanceFrom( iLoyaltyChange, sSectorX, sSectorY, bSectorZ);
#endif
}
void AffectAllTownsLoyaltyByDistanceFrom( INT32 iLoyaltyChange, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ)
{
INT16 sEventSector;
UINT8 ubTempGroupId;
INT8 bTownId;
UINT32 uiIndex;
INT32 iThisDistance;
INT32 iShortestDistance[MAX_TOWNS];
INT32 iPercentAdjustment;
INT32 iDistanceAdjustedLoyalty;
// preset shortest distances to high values prior to searching for a minimum
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; ++bTownId )
{
iShortestDistance[ bTownId ] = 999999;
}
sEventSector = CALCULATE_STRATEGIC_INDEX( sSectorX, sSectorY );
// need a temporary group create to use for laying down distance paths
ubTempGroupId = CreateNewPlayerGroupDepartingFromSector( (UINT8) sSectorX, (UINT8) sSectorY );
// calc distance to the event sector from EACH SECTOR of each town, keeping only the shortest one for every town
uiIndex = 0;
while( pTownNamesList[ uiIndex ] != 0 )
{
bTownId = (UINT8) pTownNamesList[ uiIndex ];
// Flugente: added to speed up function, remove if removed in loop below this one
// doesn't affect towns where player hasn't established a "presence" yet
if ( !gTownLoyalty[bTownId].fStarted )
{
++uiIndex;
continue;
}
// skip path test if distance is already known to be zero to speed this up a bit
if (iShortestDistance[ bTownId ] > 0 )
{
// calculate across how many sectors the fastest travel path from event to this town sector
iThisDistance = FindStratPath( sEventSector, ( INT16 )pTownLocationsList[ uiIndex ], ubTempGroupId, FALSE );
if (iThisDistance < iShortestDistance[ bTownId ])
{
iShortestDistance[ bTownId ] = iThisDistance;
}
}
++uiIndex;
}
// must always remove that temporary group!
RemoveGroup( ubTempGroupId );
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; ++bTownId )
{
// doesn't affect towns where player hasn't established a "presence" yet
if (!gTownLoyalty[ bTownId ].fStarted)
{
continue;
}
// if event was underground, double effective distance
if (bSectorZ != 0)
{
iShortestDistance[ bTownId ] *= 2;
}
// This number here controls how many sectors away is the "norm" for the unadjusted loyalty change value
if (iShortestDistance[ bTownId ] < LOYALTY_EVENT_DISTANCE_THRESHOLD)
{
// add 25% per sector below the threshold
iPercentAdjustment = 25 * (LOYALTY_EVENT_DISTANCE_THRESHOLD - iShortestDistance[ bTownId ]);
}
else if (iShortestDistance[ bTownId ] > LOYALTY_EVENT_DISTANCE_THRESHOLD)
{
// subtract 10% per sector above the threshold
iPercentAdjustment = -10 * (iShortestDistance[ bTownId ] - LOYALTY_EVENT_DISTANCE_THRESHOLD);
// don't allow it to go below -100, that would change the sign of the loyalty effect!
if (iPercentAdjustment < -100)
{
iPercentAdjustment = -100;
}
}
else
{
// no distance adjustment necessary
iPercentAdjustment = 0;
}
// calculate loyalty affects as adjusted for distance to this town
iDistanceAdjustedLoyalty = (iLoyaltyChange * (100 + iPercentAdjustment)) / 100;
if (iDistanceAdjustedLoyalty == 0)
{
// no measurable effect, skip this town
continue;
}
if ( iDistanceAdjustedLoyalty > 0)
{
IncrementTownLoyalty( bTownId, iDistanceAdjustedLoyalty );
}
else
{
// the decrement amount MUST be positive
iDistanceAdjustedLoyalty *= -1;
DecrementTownLoyalty( bTownId, iDistanceAdjustedLoyalty );
}
}
}
void AffectClosestTownLoyaltyByDistanceFrom( INT32 iLoyaltyChange, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ )
{
INT16 sEventSector;
UINT8 ubTempGroupId;
INT8 bTownId;
UINT32 uiIndex;
INT32 iThisDistance;
INT32 iShortestDistance[MAX_TOWNS];
INT32 iPercentAdjustment;
INT32 iDistanceAdjustedLoyalty;
INT32 sDistanceToClosestTown = 999999;
INT8 bClosestTown = NUM_TOWNS;
// preset shortest distances to high values prior to searching for a minimum
for ( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; ++bTownId )
{
iShortestDistance[bTownId] = 999999;
}
sEventSector = CALCULATE_STRATEGIC_INDEX( sSectorX, sSectorY );
// need a temporary group create to use for laying down distance paths
ubTempGroupId = CreateNewPlayerGroupDepartingFromSector( (UINT8)sSectorX, (UINT8)sSectorY );
// calc distance to the event sector from EACH SECTOR of each town, keeping only the shortest one for every town
uiIndex = 0;
while ( pTownNamesList[uiIndex] != 0 )
{
bTownId = (UINT8)pTownNamesList[uiIndex];
// Flugente: added to speed up function, remove if removed in loop below this one
// doesn't affect towns where player hasn't established a "presence" yet
if ( !gTownLoyalty[bTownId].fStarted )
{
++uiIndex;
continue;
}
// skip path test if distance is already known to be zero to speed this up a bit
if ( iShortestDistance[bTownId] > 0 )
{
// calculate across how many sectors the fastest travel path from event to this town sector
iThisDistance = FindStratPath( sEventSector, (INT16)pTownLocationsList[uiIndex], ubTempGroupId, FALSE );
if ( iThisDistance < iShortestDistance[bTownId] )
{
iShortestDistance[bTownId] = iThisDistance;
}
if ( iThisDistance < sDistanceToClosestTown )
{
bClosestTown = bTownId;
sDistanceToClosestTown = iThisDistance;
}
}
++uiIndex;
}
// must always remove that temporary group!
RemoveGroup( ubTempGroupId );
if ( bClosestTown < NUM_TOWNS )
{
// if event was underground, double effective distance
if ( bSectorZ != 0 )
{
sDistanceToClosestTown *= 2;
}
// This number here controls how many sectors away is the "norm" for the unadjusted loyalty change value
if ( sDistanceToClosestTown < LOYALTY_EVENT_DISTANCE_THRESHOLD )
{
// add 25% per sector below the threshold
iPercentAdjustment = 25 * ( LOYALTY_EVENT_DISTANCE_THRESHOLD - sDistanceToClosestTown );
}
else if ( sDistanceToClosestTown > LOYALTY_EVENT_DISTANCE_THRESHOLD )
{
// subtract 10% per sector above the threshold
iPercentAdjustment = -10 * ( sDistanceToClosestTown - LOYALTY_EVENT_DISTANCE_THRESHOLD );
// don't allow it to go below -100, that would change the sign of the loyalty effect!
if ( iPercentAdjustment < -100 )
{
iPercentAdjustment = -100;
}
}
else
{
// no distance adjustment necessary
iPercentAdjustment = 0;
}
// calculate loyalty affects as adjusted for distance to this town
iDistanceAdjustedLoyalty = ( iLoyaltyChange * ( 100 + iPercentAdjustment ) ) / 100;
if ( iDistanceAdjustedLoyalty == 0 )
{
// no measurable effect, skip this town
return;
}
if ( iDistanceAdjustedLoyalty > 0 )
{
IncrementTownLoyalty( bClosestTown, iDistanceAdjustedLoyalty );
}
else
{
// the decrement amount MUST be positive
iDistanceAdjustedLoyalty *= -1;
DecrementTownLoyalty( bClosestTown, iDistanceAdjustedLoyalty );
}
}
}
// to be called whenever player gains control of a sector in any way
void CheckIfEntireTownHasBeenLiberated( INT8 bTownId, INT16 sSectorX, INT16 sSectorY )
{
// the whole town is under our control, check if we never libed this town before
if ( !gTownLoyalty[ bTownId ].fLiberatedAlready && IsTownUnderCompleteControlByPlayer ( bTownId ) )
{
if ( MilitiaTrainingAllowedInSector( sSectorX, sSectorY, 0 ) )
{
// give a loyalty bonus
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_LIBERATE_WHOLE_TOWN, sSectorX, sSectorY, 0 );
// set fact is has been lib'ed and set history event
AddHistoryToPlayersLog( HISTORY_LIBERATED_TOWN, bTownId, GetWorldTotalMin(), sSectorX, sSectorY );
#ifdef JA2UB
//Ja25: No meanhwiles
#else
HandleMeanWhileEventPostingForTownLiberation( bTownId );
#endif
// Flugente: campaign stats: if we captured an entire city for the first time, take note of that. We don't have to note the city here, as that is obvious from the sector anyway
gCurrentIncident.usOneTimeEventFlags |= INCIDENT_ONETIMEEVENT_CITY_LIBERATED;
}
// even taking over non-trainable "towns" like Orta/Tixa for the first time should count as "player activity"
if ( gGameOptions.ubDifficultyLevel == DIF_LEVEL_HARD || gGameOptions.ubDifficultyLevel == DIF_LEVEL_INSANE)
//if ( gGameOptions.ubDifficultyLevel >= DIF_LEVEL_HARD )
{
UpdateLastDayOfPlayerActivity( ( UINT16 ) ( GetWorldDay() + 4 ) );
}
else
{
UpdateLastDayOfPlayerActivity( ( UINT16 ) ( GetWorldDay() + 2 ) );
}
// set flag even for towns where you can't train militia, useful for knowing Orta/Tixa were previously controlled
gTownLoyalty[ bTownId ].fLiberatedAlready = TRUE;
}
}
void CheckIfEntireTownHasBeenLost( INT8 bTownId, INT16 sSectorX, INT16 sSectorY )
{
// NOTE: only towns which allow you to train militia are important enough to get
// reported here (and they're the only ones you can protect)
if ( MilitiaTrainingAllowedInSector( sSectorX, sSectorY, 0 ) && IsTownUnderCompleteControlByEnemy(bTownId) )
{
#ifdef JA2UB
//Ja25 No meanwhile
#else
// the whole town is under enemy control, check if we libed this town before
if ( gTownLoyalty[ bTownId ].fLiberatedAlready )
{
HandleMeanWhileEventPostingForTownLoss( bTownId );
}
#endif
}
}
void HandleLoyaltyChangeForNPCAction( UINT8 ubNPCProfileId )
{
//move to lua scripts StrategicTownLoyalty.lua
#ifdef LUA_STRATEGY_TOWN_LOYALTY
LetHandleLoyaltyChangeForNPCAction( ubNPCProfileId, 0 );
#else
switch ( ubNPCProfileId )
{
case MIGUEL:
// Omerta loyalty increases when Miguel receives letter from Enrico
IncrementTownLoyalty( OMERTA, LOYALTY_BONUS_MIGUEL_READS_LETTER );
break;
case DOREEN:
// having freed the child labourers... she is releasing them herself!
IncrementTownLoyalty( DRASSEN, LOYALTY_BONUS_CHILDREN_FREED_DOREEN_SPARED );
break;
case MARTHA:
// if Joey is still alive
if ( gMercProfiles[ JOEY ].bMercStatus != MERC_IS_DEAD )
{
IncrementTownLoyalty( CAMBRIA, LOYALTY_BONUS_MARTHA_WHEN_JOEY_RESCUED );
}
break;
case KEITH:
// Hillbilly problem solved
IncrementTownLoyalty( CAMBRIA, LOYALTY_BONUS_KEITH_WHEN_HILLBILLY_SOLVED );
break;
case YANNI:
// Chalice of Chance returned to Chitzena
IncrementTownLoyalty( CHITZENA, LOYALTY_BONUS_YANNI_WHEN_CHALICE_RETURNED_LOCAL );
// NOTE: This affects Chitzena,too, a second time, so first value is discounted for it
IncrementTownLoyaltyEverywhere( LOYALTY_BONUS_YANNI_WHEN_CHALICE_RETURNED_GLOBAL );
break;
#ifdef JA2UB
// ja25 UB
#else
case AUNTIE:
// Bloodcats killed
IncrementTownLoyalty( ALMA, LOYALTY_BONUS_AUNTIE_WHEN_BLOODCATS_KILLED );
break;
#endif
case MATT:
// Brother Dynamo freed
IncrementTownLoyalty( ALMA, LOYALTY_BONUS_MATT_WHEN_DYNAMO_FREED );
break;
}
#endif
}
// set the location of the first encounter with enemy
void SetTheFirstBattleSector( INT16 sSectorValue )
{
if( sWorldSectorLocationOfFirstBattle == 0 )
{
sWorldSectorLocationOfFirstBattle = sSectorValue;
}
return;
}
// did first battle take place here
BOOLEAN DidFirstBattleTakePlaceInThisTown( INT8 bTownId )
{
INT8 bTownBattleId = 0;
// get town id for sector
bTownBattleId = GetTownIdForSector( ( INT16 ) ( sWorldSectorLocationOfFirstBattle % MAP_WORLD_X ), ( INT16 ) ( sWorldSectorLocationOfFirstBattle / MAP_WORLD_X ) );
return( bTownId == bTownBattleId );
}
UINT32 PlayerStrength( void )
{
SOLDIERTYPE *pSoldier;
UINT32 uiStrength, uiTotal = 0;
for ( SoldierID ubLoop = gTacticalStatus.Team[ gbPlayerNum ].bFirstID; ubLoop <= gTacticalStatus.Team[ gbPlayerNum ].bLastID; ++ubLoop )
{
pSoldier = ubLoop;
if ( pSoldier->bActive )
{
if ( pSoldier->bInSector || (pSoldier->flags.fBetweenSectors && SECTORX( pSoldier->ubPrevSectorID ) == gWorldSectorX && SECTORY( pSoldier->ubPrevSectorID ) == gWorldSectorY && (pSoldier->bSectorZ == gbWorldSectorZ)) )
{
// count this person's strength (condition), calculated as life reduced up to half according to maxbreath
uiStrength = pSoldier->stats.bLife * ( pSoldier->bBreathMax + 100 ) / 200;
uiTotal += uiStrength;
}
}
}
return( uiTotal );
}
UINT32 EnemyStrength( void )
{
SOLDIERTYPE * pSoldier;
UINT32 uiStrength, uiTotal = 0;
for ( SoldierID ubLoop = gTacticalStatus.Team[ ENEMY_TEAM ].bFirstID; ubLoop <= gTacticalStatus.Team[ CIV_TEAM ].bLastID; ++ubLoop )
{
pSoldier = ubLoop;
if ( pSoldier->bActive && pSoldier->bInSector && !pSoldier->aiData.bNeutral )
{
// count this person's strength (condition), calculated as life reduced up to half according to maxbreath
uiStrength = pSoldier->stats.bLife * ( pSoldier->bBreathMax + 100 ) / 200;
uiTotal += uiStrength;
}
}
return( uiTotal );
}
//Function assumes that mercs have retreated already. Handles two cases, one for general merc retreat
//which slightly demoralizes the mercs, the other handles abandonment of militia forces which poses
//as a serious loyalty penalty.
void HandleLoyaltyImplicationsOfMercRetreat( INT8 bRetreatCode, INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ )
{
if ( NumNonPlayerTeamMembersInSector( sSectorX, sSectorY, MILITIA_TEAM ) )
{
//Big morale penalty!
HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ABANDON_MILITIA, sSectorX, sSectorY, (INT8)sSectorZ );
}
//Standard retreat penalty
if ( bRetreatCode == RETREAT_TACTICAL_TRAVERSAL )
{
// if not worse than 2:1 odds, then penalize morale
// SANDRO - Set the odds based on difficulty level
UINT8 DiffLevel = gGameOptions.ubDifficultyLevel;
if ( DiffLevel > DIF_LEVEL_INSANE )
DiffLevel = 1;
// sevenfm: if enemy was alerted
if (gTacticalStatus.fEnemyInSector &&
gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition &&
((PlayerStrength() * (2 + DiffLevel)) >= EnemyStrength()))
{
HandleMoraleEvent(NULL, MORALE_RAN_AWAY, sSectorX, sSectorY, (INT8)sSectorZ);
}
}
else
{
HandleMoraleEvent( NULL, MORALE_RAN_AWAY, sSectorX, sSectorY, (INT8)sSectorZ );
}
// Flugente: dynamic opinion: mercs retreated. Someone has to be blamed for this shameful defeat!
// only do this if the enemy actually noticed us - no need to punish the player for a successful stealth operation
if (gGameExternalOptions.fDynamicOpinions && gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition )
{
HandleDynamicOpinionRetreat();
}
}
void MaximizeLoyaltyForDeidrannaKilled(void)
{
INT8 bTownId;
// max out loyalty to player all across Arulco
for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
{
// it's possible one of the towns still has creature problems, but it's too much of a pain to worry about it now
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 );
}
}