1 #include "Font_Control.h"
2 #include "LoadSaveData.h"
3 #include "MapScreen.h"
4 #include "Strategic_Town_Loyalty.h"
5 #include "StrategicMap.h"
6 #include "Overhead.h"
7 #include "Queen_Command.h"
8 #include "Animation_Data.h"
9 #include "Quests.h"
10 #include "Message.h"
11 #include "LOS.h"
12 #include "World_Items.h"
13 #include "Tactical_Save.h"
14 #include "Soldier_Profile.h"
15 #include "Handle_Items.h"
16 #include "Random.h"
17 #include "Strategic_Movement.h"
18 #include "Strategic_Pathing.h"
19 #include "Game_Clock.h"
20 #include "Game_Event_Hook.h"
21 #include "Morale.h"
22 #include "Text.h"
23 #include "MessageBoxScreen.h"
24 #include "Creature_Spreading.h"
25 #include "Town_Militia.h"
26 #include "History.h"
27 #include "Meanwhile.h"
28 #include "GameSettings.h"
29 #include "Strategic_Status.h"
30 #include "MemMan.h"
31 #include "Debug.h"
32 #include "FileMan.h"
33 #include "Logger.h"
34 
35 #include <string_theory/string>
36 
37 #include <algorithm>
38 #include <iterator>
39 #include <vector>
40 
41 // the max loyalty rating for any given town
42 #define MAX_LOYALTY_VALUE 100
43 
44 // loyalty Omerta drops to and maxes out at if the player betrays the rebels
45 #define HOSTILE_OMERTA_LOYALTY_RATING 10
46 
47 #define LOYALTY_EVENT_DISTANCE_THRESHOLD 3 // in sectors
48 
49 
50 // effect of unintentional killing /100 vs intentional murder
51 #define REDUCTION_FOR_UNINTENTIONAL_KILLING 50
52 // the effect of a murder by rebel / 100 vs player murdering a civ
53 #define REDUCTION_FOR_MURDER_BY_REBEL 70
54 // reduction % for being falsely blamed for a murder we didn't do, out of 100
55 #define REDUCTION_FOR_MURDER_NOT_OUR_FAULT 50
56 // reduction % if enemies kills civs in our sector / 100
57 #define REDUCTION_FOR_MURDER_OF_INNOCENT_BY_ENEMY_IN_OUR_SECTOR 25
58 // how much worse loyalty hit is for trauma of someone killed by a monster
59 #define MULTIPLIER_FOR_MURDER_BY_MONSTER 2
60 
61 
62 
63 // gain for hiring an NPC from a particular town (represents max. at 100% town attachment)
64 #define MULTIPLIER_LOCAL_RPC_HIRED 25 // 5%
65 
66 // multiplier for causing pts of damage directly done to a building
67 #define MULTIPLIER_FOR_DAMAGING_A_BUILDING 10 // 50 pts = 1%
68 // multiplier for not preventing pts of damage to a building
69 #define MULTIPLIER_FOR_NOT_PREVENTING_BUILDING_DAMAGE 3 // 167 pts = 1%
70 
71 // divisor for dmg to a building by allied rebel
72 #define DIVISOR_FOR_REBEL_BUILDING_DMG 2
73 
74 
75 // town loyalty table
76 TOWN_LOYALTY gTownLoyalty[ NUM_TOWNS ];
77 
78 
79 // Town names and locations
80 std::vector<TownSectorInfo> g_town_sectors;
81 
82 
83 #define BASIC_COST_FOR_CIV_MURDER	(10 * GAIN_PTS_PER_LOYALTY_PT)
84 
85 UINT32 uiPercentLoyaltyDecreaseForCivMurder[]={
86 	// These get multiplied by GAIN_PTS_PER_LOYALTY_PT so they're in % of loyalty decrease (for an average town)
87 	(5 * GAIN_PTS_PER_LOYALTY_PT), // fat civ
88 	(7 * GAIN_PTS_PER_LOYALTY_PT), // man civ
89 	(8 * GAIN_PTS_PER_LOYALTY_PT), // min civ
90 	(10 * GAIN_PTS_PER_LOYALTY_PT), // dress (woman)
91 	(20 * GAIN_PTS_PER_LOYALTY_PT), // hat kid
92 	(20 * GAIN_PTS_PER_LOYALTY_PT), // kid
93 	(20 * GAIN_PTS_PER_LOYALTY_PT), // cripple
94 	(1 * GAIN_PTS_PER_LOYALTY_PT), // cow
95 };
96 
97 
98 // 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
99 // it primarily controls the RATE of loyalty change in each town: the loyalty effect of the same events depends on it
100 UINT8 gubTownRebelSentiment[ NUM_TOWNS ] =
101 {
102 	0, // not a town - blank sector index
103 	90, // OMERTA, - They ARE the rebels!!!
104 	30, // DRASSEN, - Rebel friendly, makes it pretty easy to get first mine's income going at the start
105 	12, // ALMA - Military town, high loyalty to Queen, need quests to get 100%
106 	15, // GRUMM, - Close to Meduna, strong influence
107 	20, // TIXA, - Not a real town
108 	15, // CAMBRIA, - Artificially much lower 'cause it's big and central and too easy to get loyalty up there
109 	20, // SAN_MONA, - Neutral ground, loyalty doesn't vary
110 	20, // ESTONI, - Not a real town
111 	20, // ORTA, - Not a real town
112 	12, // BALIME, - Rich town, high loyalty to Queen
113 	10, // MEDUNA, - Enemy HQ, for God's sake!
114 	35, // CHITZENA, - Artificially high 'cause there's not enough fights near it to get the loyalty up otherwise
115 };
116 
117 BOOLEAN gfTownUsesLoyalty[ NUM_TOWNS ] =
118 {
119 	FALSE, // not a town - blank sector index
120 	TRUE, // OMERTA
121 	TRUE, // DRASSEN
122 	TRUE, // ALMA
123 	TRUE, // GRUMM
124 	FALSE, // TIXA
125 	TRUE, // CAMBRIA
126 	FALSE, // SAN_MONA
127 	FALSE, // ESTONI
128 	FALSE, // ORTA
129 	TRUE, // BALIME
130 	TRUE, // MEDUNA
131 	TRUE, // CHITZENA
132 };
133 
134 // location of first enocunter with enemy
135 INT16 sWorldSectorLocationOfFirstBattle = 0;
136 
137 
InitTownLoyalty(void)138 void InitTownLoyalty( void )
139 {
140 	UINT8 ubTown = 0;
141 
142 	// set up town loyalty table
143 	for( ubTown = FIRST_TOWN; ubTown < NUM_TOWNS; ubTown++ )
144 	{
145 		gTownLoyalty[ ubTown ].ubRating = 0;
146 		gTownLoyalty[ ubTown ].sChange = 0;
147 		gTownLoyalty[ ubTown ].fStarted = FALSE;
148 		gTownLoyalty[ ubTown ].fLiberatedAlready = FALSE;
149 	}
150 }
151 
152 
StartTownLoyaltyIfFirstTime(INT8 bTownId)153 void StartTownLoyaltyIfFirstTime( INT8 bTownId )
154 {
155 	Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
156 
157 	// if loyalty tracking hasn't yet been started for this town, and the town does use loyalty
158 	if (!gTownLoyalty[ bTownId ].fStarted && gfTownUsesLoyalty[ bTownId ])
159 	{
160 		// set starting town loyalty now, equally to that town's current rebel sentiment - not all towns begin equally loyal
161 		gTownLoyalty[ bTownId ].ubRating = gubTownRebelSentiment[ bTownId ];
162 
163 		// if player hasn't made contact with Miguel yet, or the rebels hate the player
164 		if (!CheckFact(FACT_MIGUEL_READ_LETTER, 0) || CheckFact( FACT_REBELS_HATE_PLAYER, 0 ))
165 		{
166 			// if town is Omerta
167 			if (bTownId == OMERTA)
168 			{
169 				// start loyalty there at 0, since rebels distrust the player until Miguel receives the letter
170 				gTownLoyalty[ bTownId ].ubRating  = 0;
171 			}
172 			else
173 			{
174 				// starting loyalty is halved - locals not sure what to make of the player's presence
175 				gTownLoyalty[ bTownId ].ubRating /= 2;
176 			}
177 		}
178 
179 		gTownLoyalty[ bTownId ].sChange = 0;
180 
181 		// remember we've started
182 		gTownLoyalty[ bTownId ].fStarted = TRUE;
183 	}
184 }
185 
186 
187 // set a specified town's loyalty rating (ignores previous loyalty value - probably NOT what you want)
SetTownLoyalty(INT8 bTownId,UINT8 ubNewLoyaltyRating)188 void SetTownLoyalty( INT8 bTownId, UINT8 ubNewLoyaltyRating )
189 {
190 	Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
191 
192 	// if the town does use loyalty
193 	if (gfTownUsesLoyalty[ bTownId ])
194 	{
195 		gTownLoyalty[ bTownId ].ubRating = ubNewLoyaltyRating;
196 		gTownLoyalty[ bTownId ].sChange = 0;
197 
198 		// this is just like starting the loyalty if it happens first
199 		gTownLoyalty[ bTownId ].fStarted = TRUE;
200 	}
201 }
202 
203 
204 static void UpdateTownLoyaltyRating(INT8 bTownId);
205 
206 
207 // increments the town's loyalty rating by that many HUNDREDTHS of loyalty pts
IncrementTownLoyalty(INT8 bTownId,UINT32 uiLoyaltyIncrease)208 void IncrementTownLoyalty( INT8 bTownId, UINT32 uiLoyaltyIncrease )
209 {
210 	UINT32 uiRemainingIncrement;
211 	INT16 sThisIncrement;
212 
213 
214 	Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
215 
216 	// doesn't affect towns where player hasn't established a "presence" yet
217 	if (!gTownLoyalty[ bTownId ].fStarted)
218 	{
219 		return;
220 	}
221 
222 	// modify loyalty change by town's individual attitude toward rebelling (20 is typical)
223 	uiLoyaltyIncrease *= (5 * gubTownRebelSentiment[ bTownId ]);
224 	uiLoyaltyIncrease /= 100;
225 
226 
227 	// this whole thing is a hack to avoid rolling over the -32 to 32k range on the sChange value
228 	// only do a maximum of 10000 pts at a time...
229 	uiRemainingIncrement = uiLoyaltyIncrease;
230 	while ( uiRemainingIncrement )
231 	{
232 		sThisIncrement = ( INT16 ) MIN( uiRemainingIncrement, 10000 );
233 
234 		// up the gain value
235 		gTownLoyalty[ bTownId ].sChange += (INT16) sThisIncrement;
236 		// update town value now
237 		UpdateTownLoyaltyRating( bTownId );
238 
239 		uiRemainingIncrement -= sThisIncrement;
240 	}
241 }
242 
243 // decrements the town's loyalty rating by that many HUNDREDTHS of loyalty pts
244 //NOTE: This function expects a POSITIVE number for a decrease!!!
DecrementTownLoyalty(INT8 bTownId,UINT32 uiLoyaltyDecrease)245 void DecrementTownLoyalty( INT8 bTownId, UINT32 uiLoyaltyDecrease )
246 {
247 	UINT32 uiRemainingDecrement;
248 	INT16 sThisDecrement;
249 
250 
251 	Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
252 
253 	// doesn't affect towns where player hasn't established a "presence" yet
254 	if (!gTownLoyalty[ bTownId ].fStarted)
255 	{
256 		return;
257 	}
258 
259 	// modify loyalty change by town's individual attitude toward rebelling (20 is typical)
260 	uiLoyaltyDecrease *= 100;
261 	uiLoyaltyDecrease /= (5 * gubTownRebelSentiment[ bTownId ]);
262 
263 	// this whole thing is a hack to avoid rolling over the -32 to 32k range on the sChange value
264 	// only do a maximum of 10000 pts at a time...
265 	uiRemainingDecrement = uiLoyaltyDecrease;
266 	while ( uiRemainingDecrement )
267 	{
268 		sThisDecrement = ( INT16 ) MIN( uiRemainingDecrement, 10000 );
269 
270 		// down the gain value
271 		gTownLoyalty[ bTownId ].sChange -= sThisDecrement;
272 		// update town value now
273 		UpdateTownLoyaltyRating( bTownId );
274 
275 		uiRemainingDecrement -= sThisDecrement;
276 	}
277 }
278 
279 
280 // update town loyalty rating based on gain values
UpdateTownLoyaltyRating(INT8 bTownId)281 static void UpdateTownLoyaltyRating(INT8 bTownId)
282 {
283 	// check gain value and update loyaty
284 	UINT8 ubOldLoyaltyRating = 0;
285 	INT16 sRatingChange = 0;
286 	UINT8 ubMaxLoyalty = 0;
287 
288 
289 	Assert( ( bTownId >= FIRST_TOWN ) && ( bTownId < NUM_TOWNS ) );
290 
291 	// remember previous loyalty value
292 	ubOldLoyaltyRating = gTownLoyalty[ bTownId ].ubRating;
293 
294 	sRatingChange = gTownLoyalty[ bTownId ].sChange / GAIN_PTS_PER_LOYALTY_PT;
295 
296 	// if loyalty is ready to increase
297 	if( sRatingChange > 0 )
298 	{
299 		// if the town is Omerta, and the rebels are/will become hostile
300 		if ((bTownId == OMERTA) && (gTacticalStatus.fCivGroupHostile[ REBEL_CIV_GROUP ] != CIV_GROUP_NEUTRAL))
301 		{
302 			// maximum loyalty is much less than normal
303 			ubMaxLoyalty = HOSTILE_OMERTA_LOYALTY_RATING;
304 		}
305 		else
306 		{
307 			ubMaxLoyalty = MAX_LOYALTY_VALUE;
308 		}
309 
310 		// check if we'd be going over the max
311 		if( (gTownLoyalty[ bTownId ].ubRating + sRatingChange ) >= ubMaxLoyalty )
312 		{
313 			// set to max and null out gain pts
314 			gTownLoyalty[ bTownId ].ubRating = ubMaxLoyalty;
315 			gTownLoyalty[ bTownId ].sChange = 0;
316 		}
317 		else
318 		{
319 			// increment loyalty rating, reduce sChange
320 			gTownLoyalty[ bTownId ].ubRating += sRatingChange;
321 			gTownLoyalty[ bTownId ].sChange %= GAIN_PTS_PER_LOYALTY_PT;
322 		}
323 	}
324 	else
325 	// if loyalty is ready to decrease
326 	if( sRatingChange < 0 )
327 	{
328 		// check if we'd be going below zero
329 		if( ( gTownLoyalty[ bTownId ].ubRating + sRatingChange ) < 0 )
330 		{
331 			// set to zero and null out gain pts
332 			gTownLoyalty[ bTownId ].ubRating = 0;
333 			gTownLoyalty[ bTownId ].sChange = 0;
334 		}
335 		else
336 		{
337 			// decrement loyalty rating, reduce sChange
338 			gTownLoyalty[ bTownId ].ubRating += sRatingChange;
339 			gTownLoyalty[ bTownId ].sChange %= GAIN_PTS_PER_LOYALTY_PT;
340 		}
341 	}
342 
343 
344 	// check old aginst new, if diff, dirty map panel
345 	if( ubOldLoyaltyRating != gTownLoyalty[ bTownId ].ubRating )
346 	{
347 		fMapPanelDirty = TRUE;
348 	}
349 }
350 
351 
352 static void AffectAllTownsLoyaltyByDistanceFrom(INT32 iLoyaltyChange, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ);
353 
354 
HandleMurderOfCivilian(const SOLDIERTYPE * const pSoldier)355 void HandleMurderOfCivilian(const SOLDIERTYPE* const pSoldier)
356 {
357 	// handle the impact on loyalty of the murder of a civilian
358 	INT32 iLoyaltyChange = 0;
359 	INT8 bSeenState = 0;
360 	UINT32 uiChanceFalseAccusal = 0;
361 	INT8 bKillerTeam = 0;
362 	BOOLEAN fIncrement = FALSE;
363 
364 	// attacker CAN be NOBODY...  Don't treat is as murder if NOBODY killed us...
365 	SOLDIERTYPE* const killer = pSoldier->attacker;
366 	if (killer == NULL) return;
367 
368 	// ignore murder of non-civilians!
369 	if ( ( pSoldier->bTeam != CIV_TEAM ) || ( pSoldier->ubBodyType == CROW ) )
370 	{
371 		return;
372 	}
373 
374 	// if this is a profiled civilian NPC
375 	if ( pSoldier->ubProfile != NO_PROFILE )
376 	{
377 		// ignore murder of NPCs if they're not loyal to the rebel cause - they're really just enemies in civilian clothing
378 		if ( gMercProfiles[ pSoldier->ubProfile ].ubMiscFlags3 & PROFILE_MISC_FLAG3_TOWN_DOESNT_CARE_ABOUT_DEATH )
379 		{
380 			return;
381 		}
382 	}
383 
384 	// if civilian belongs to a civilian group
385 	if ( pSoldier->ubCivilianGroup != NON_CIV_GROUP )
386 	{
387 		// and it's one that is hostile to the player's cause
388 		switch ( pSoldier->ubCivilianGroup )
389 		{
390 			case KINGPIN_CIV_GROUP:
391 			case ALMA_MILITARY_CIV_GROUP:
392 			case HICKS_CIV_GROUP:
393 			case WARDEN_CIV_GROUP:
394 				return;
395 		}
396 	}
397 
398 	// set killer team
399 	bKillerTeam = killer->bTeam;
400 
401 	// if the player did the killing
402 	if( bKillerTeam == OUR_TEAM )
403 	{
404 		// apply morale penalty for killing a civilian!
405 		HandleMoraleEvent(killer, MORALE_KILLED_CIVILIAN, killer->sSectorX, killer->sSectorY, killer->bSectorZ);
406 	}
407 
408 	UINT8 const bTownId = GetTownIdForSector(SECTOR(pSoldier->sSectorX, pSoldier->sSectorY));
409 
410 	// if civilian is NOT in a town
411 	if( bTownId == BLANK_SECTOR )
412 	{
413 		return;
414 	}
415 
416 	// check if this town does use loyalty (to skip a lot of unnecessary computation)
417 	if (!gfTownUsesLoyalty[ bTownId ])
418 	{
419 		return;
420 	}
421 
422 
423 	if( ( pSoldier->ubBodyType >= FATCIV) && ( pSoldier->ubBodyType <= COW ) )
424 	{
425 		// adjust value for killer and type
426 		iLoyaltyChange = uiPercentLoyaltyDecreaseForCivMurder[ pSoldier->ubBodyType - FATCIV ];
427 	}
428 	else
429 	{
430 		iLoyaltyChange = BASIC_COST_FOR_CIV_MURDER;
431 	}
432 
433 	if (!pSoldier->fIntendedTarget)
434 	{
435 		// accidental killing, reduce value
436 		iLoyaltyChange *= REDUCTION_FOR_UNINTENTIONAL_KILLING;
437 		iLoyaltyChange /= 100;
438 	}
439 
440 	// check if LOS between any civ, killer and killed
441 	// if so, then do not adjust
442 
443 	FOR_EACH_IN_TEAM(pCivSoldier, CIV_TEAM)
444 	{
445 		if ( pCivSoldier == pSoldier )
446 		{
447 			continue;
448 		}
449 
450 		// killer seen by civ?
451 		if (SoldierToSoldierLineOfSightTest(pCivSoldier, killer, STRAIGHT_RANGE, TRUE) != 0)
452 		{
453 			bSeenState |= 1;
454 		}
455 
456 		// victim seen by civ?
457 		if( SoldierToSoldierLineOfSightTest( pCivSoldier, pSoldier, STRAIGHT_RANGE, TRUE ) != 0 )
458 		{
459 			bSeenState |= 2;
460 		}
461 	}
462 
463 	// if player didn't do it
464 	if( bKillerTeam != OUR_TEAM )
465 	{
466 		// If the murder is not fully witnessed, there's a chance of player being blamed for it even if it's not his fault
467 		switch (bSeenState)
468 		{
469 			case 0:
470 				// nobody saw anything.  When body is found, chance player is blamed depends on the town's loyalty at this time
471 				uiChanceFalseAccusal = MAX_LOYALTY_VALUE - gTownLoyalty[ bTownId ].ubRating;
472 				break;
473 			case 1:
474 				// civilians saw the killer, but not the victim. 10 % chance of blaming player whether or not he did it
475 				uiChanceFalseAccusal = 10;
476 				break;
477 			case 2:
478 				// civilians only saw the victim.  50/50 chance...
479 				uiChanceFalseAccusal = 50;
480 				break;
481 			case 3:
482 				// civilians have seen it all, and in this case, they're always honest
483 				uiChanceFalseAccusal = 0;
484 				break;
485 			default:
486 				Assert(FALSE);
487 				return;
488 		}
489 
490 		// check whether player is falsely accused
491 		if( Random(100) < uiChanceFalseAccusal )
492 		{
493 			// blame player whether or not he did it - set killer team as our team
494 			bKillerTeam = OUR_TEAM;
495 
496 			// not really player's fault, reduce penalty, because some will believe it to be a lie
497 			iLoyaltyChange *= REDUCTION_FOR_MURDER_NOT_OUR_FAULT;
498 			iLoyaltyChange /= 100;
499 
500 			// debug message
501 			SLOGD("You're being blamed for a death you didn't cause!");
502 		}
503 	}
504 
505 
506 	// check who town thinks killed the civ
507 	switch (bKillerTeam)
508 	{
509 		case OUR_TEAM:
510 			// town thinks player committed the murder, bad bad bad
511 			fIncrement = FALSE;
512 
513 			// debug message
514 			SLOGD("Civilian killed by friendly forces.");
515 			break;
516 
517 		case ENEMY_TEAM:
518 			// check whose sector this is
519 			if (StrategicMap[pSoldier->sSectorX + MAP_WORLD_X * pSoldier->sSectorY].fEnemyControlled)
520 			{
521 				// enemy soldiers... in enemy controlled sector.  Gain loyalty
522 				fIncrement = TRUE;
523 
524 				// debug message
525 				SLOGD("Enemy soldiers murdered a civilian. Town loyalty increases");
526 			}
527 			else
528 			{
529 				// reduce, we're expected to provide some protection, but not miracles
530 				iLoyaltyChange *= REDUCTION_FOR_MURDER_OF_INNOCENT_BY_ENEMY_IN_OUR_SECTOR;
531 				iLoyaltyChange /= 100;
532 
533 				// lose loyalty
534 				fIncrement = FALSE;
535 
536 				// debug message
537 				SLOGD("Town holds you responsible for murder by enemy.");
538 			}
539 			break;
540 
541 		case MILITIA_TEAM:
542 			// the rebels did it... check if are they on our side
543 			if (!CheckFact(FACT_REBELS_HATE_PLAYER, 0))
544 			{
545 				// on our side, penalty
546 				iLoyaltyChange *= REDUCTION_FOR_MURDER_BY_REBEL;
547 				iLoyaltyChange /= 100;
548 
549 				// lose loyalty
550 				fIncrement = FALSE;
551 
552 				// debug message
553 				SLOGD("Town holds you responsible for murder by rebels.");
554 			}
555 			break;
556 
557 	case CREATURE_TEAM:
558 			// killed by a monster - make sure it was one
559 			if (ADULTFEMALEMONSTER <= killer->ubBodyType && killer->ubBodyType <= QUEENMONSTER)
560 			{
561 				// increase for the extreme horror of being killed by a monster
562 				iLoyaltyChange *= MULTIPLIER_FOR_MURDER_BY_MONSTER;
563 
564 				// check whose sector this is
565 				if (StrategicMap[pSoldier->sSectorX + MAP_WORLD_X * pSoldier->sSectorY].fEnemyControlled)
566 				{
567 					// enemy controlled sector - gain loyalty
568 					fIncrement = TRUE;
569 				}
570 				else
571 				{
572 					// our sector - lose loyalty
573 					fIncrement = FALSE;
574 				}
575 			}
576 			break;
577 
578 		default:
579 			Assert(FALSE);
580 			return;
581 	}
582 
583 
584 	// if it's a decrement, negate the value
585 	if ( !fIncrement )
586 	{
587 		iLoyaltyChange *= -1;
588 	}
589 
590 	// this is a hack: to avoid having to adjust the values, divide by 1.75 to compensate for the distance 0
591 	iLoyaltyChange *= 100;
592 	iLoyaltyChange /= (100 + ( 25 * LOYALTY_EVENT_DISTANCE_THRESHOLD ) );
593 
594 	AffectAllTownsLoyaltyByDistanceFrom( iLoyaltyChange, pSoldier->sSectorX, pSoldier->sSectorY, pSoldier->bSectorZ );
595 }
596 
597 
598 
599 // check town and raise loyalty value for hiring a merc from a town...not a lot of a gain, but some
HandleTownLoyaltyForNPCRecruitment(SOLDIERTYPE * pSoldier)600 void HandleTownLoyaltyForNPCRecruitment( SOLDIERTYPE *pSoldier )
601 {
602 	UINT32 uiLoyaltyValue = 0;
603 
604 	UINT8 const bTownId = GetTownIdForSector(SECTOR(pSoldier->sSectorX, pSoldier->sSectorY));
605 
606 	// is the merc currently in their home town?
607 	if( bTownId == gMercProfiles[ pSoldier->ubProfile ].bTown )
608 	{
609 		// yep, value of loyalty bonus depends on his importance to this to town
610 		uiLoyaltyValue = MULTIPLIER_LOCAL_RPC_HIRED * gMercProfiles[ pSoldier->ubProfile ].bTownAttachment;
611 
612 		// increment town loyalty gain
613 		IncrementTownLoyalty( bTownId, uiLoyaltyValue );
614 
615 		// DESIGN QUESTION: How easy is it to abuse this bonus by repeatedly hiring the guy over & over
616 		// (at worst daily? even more often if terminated & rehired?)  (ARM)
617 	}
618 }
619 
RemoveRandomItemsInSector(INT16 const sSectorX,INT16 const sSectorY,INT16 const sSectorZ,UINT8 const ubChance)620 void RemoveRandomItemsInSector(INT16 const sSectorX, INT16 const sSectorY, INT16 const sSectorZ, UINT8 const ubChance)
621 {
622 	/* Stealing should fail anyway 'cause there shouldn't be a temp file for
623 	 * unvisited sectors, but let's check anyway */
624 	Assert(GetSectorFlagStatus(sSectorX, sSectorY, sSectorZ, SF_ALREADY_VISITED));
625 
626 	ST::string wSectorName = GetSectorIDString(sSectorX, sSectorY, sSectorZ, TRUE);
627 
628 	// go through list of items in sector and randomly remove them
629 
630 	// if unloaded sector
631 	if (gWorldSectorX  != sSectorX ||
632 			gWorldSectorY  != sSectorY ||
633 			gbWorldSectorZ != sSectorZ)
634 	{
635 		/* if the player has never been there, there's no temp file, and 0 items
636 		 * will get returned, preventing any stealing */
637 		std::vector<WORLDITEM> pItemList = LoadWorldItemsFromTempItemFile(sSectorX, sSectorY, sSectorZ);
638 		if (pItemList.size() == 0) return;
639 
640 		bool somethingWasStolen = false;
641 
642 		// set up item list ptrs
643 		for (WORLDITEM& wi : pItemList)
644 		{
645 			//if the item exists, and is visible and reachable, see if it should be stolen
646 			if (!wi.fExists)                          continue;
647 			if (wi.bVisible != VISIBLE)               continue;
648 			if (!(wi.usFlags & WORLD_ITEM_REACHABLE)) continue;
649 			if (Random(100) >= ubChance)               continue;
650 
651 			// remove
652 			somethingWasStolen = true;
653 			wi.fExists = FALSE;
654 
655 			SLOGD("%s stolen in %s!", ItemNames[wi.o.usItem].c_str(), wSectorName.c_str());
656 		}
657 
658 		// only save if something was stolen
659 		if (somethingWasStolen)
660 		{
661 			SaveWorldItemsToTempItemFile(sSectorX, sSectorY, sSectorZ, pItemList);
662 		}
663 	}
664 	else	// handle a loaded sector
665 	{
666 		FOR_EACH_WORLD_ITEM(wi)
667 		{
668 			// note, can't do reachable test here because we'd have to do a path call
669 			if (wi.bVisible != VISIBLE) continue;
670 			if (Random(100) >= ubChance) continue;
671 
672 			SLOGD("%s stolen in %s!", ItemNames[wi.o.usItem].c_str(), wSectorName.c_str());
673 			RemoveItemFromPool(wi);
674 		}
675 	}
676 }
677 
678 
BuildListOfTownSectors()679 void BuildListOfTownSectors()
680 {
681 	for (INT32 x = 1; x != MAP_WORLD_X - 1; ++x)
682 	{
683 		for (INT32 y = 1; y != MAP_WORLD_Y - 1; ++y)
684 		{
685 			INT8 const town = StrategicMap[CALCULATE_STRATEGIC_INDEX(x, y)].bNameId;
686 			if (town < FIRST_TOWN || NUM_TOWNS <= town) continue;
687 			g_town_sectors.push_back(
688 				TownSectorInfo{ (UINT8)town, SECTOR(x, y) }
689 			);
690 		}
691 	}
692 }
693 
694 
SaveStrategicTownLoyaltyToSaveGameFile(HWFILE const f)695 void SaveStrategicTownLoyaltyToSaveGameFile(HWFILE const f)
696 {
697 	//Save the Town Loyalty
698 	FOR_EACH(TOWN_LOYALTY const, i, gTownLoyalty)
699 	{
700 		BYTE  data[26];
701 		DataWriter d{data};
702 		INJ_U8(  d, i->ubRating)
703 		INJ_SKIP(d, 1)
704 		INJ_I16( d, i->sChange)
705 		INJ_BOOL(d, i->fStarted)
706 		INJ_SKIP(d, 1)
707 		INJ_BOOL(d, i->fLiberatedAlready)
708 		INJ_SKIP(d, 19)
709 		Assert(d.getConsumed() == lengthof(data));
710 
711 		FileWrite(f, data, sizeof(data));
712 	}
713 }
714 
715 
LoadStrategicTownLoyaltyFromSavedGameFile(HWFILE const f)716 void LoadStrategicTownLoyaltyFromSavedGameFile(HWFILE const f)
717 {
718 	//Restore the Town Loyalty
719 	FOR_EACH(TOWN_LOYALTY, i, gTownLoyalty)
720 	{
721 		BYTE data[26];
722 		FileRead(f, data, sizeof(data));
723 
724 		DataReader d{data};
725 		EXTR_U8(  d, i->ubRating)
726 		EXTR_SKIP(d, 1)
727 		EXTR_I16( d, i->sChange)
728 		EXTR_BOOL(d, i->fStarted)
729 		EXTR_SKIP(d, 1)
730 		EXTR_BOOL(d, i->fLiberatedAlready)
731 		EXTR_SKIP(d, 19)
732 		Assert(d.getConsumed() == lengthof(data));
733 	}
734 }
735 
736 
ReduceLoyaltyForRebelsBetrayed(void)737 void ReduceLoyaltyForRebelsBetrayed(void)
738 {
739 	INT8 bTownId;
740 
741 	// reduce loyalty to player all across Arulco
742 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
743 	{
744 		if (bTownId == OMERTA)
745 		{
746 			// if not already really low
747 			if (gTownLoyalty[ bTownId ].ubRating > HOSTILE_OMERTA_LOYALTY_RATING)
748 			{
749 				// loyalty in Omerta tanks big time, and will stay low permanently since this becomes its maximum
750 				SetTownLoyalty(bTownId, HOSTILE_OMERTA_LOYALTY_RATING);
751 				// note that rebel sentiment isn't affected - they remain loyal to themselves, after all!
752 			}
753 		}
754 		else
755 		{
756 
757 			// loyalty in other places is also strongly affected by this falling out with rebels, but this is not permanent
758 			SetTownLoyalty(bTownId, (UINT8) (gTownLoyalty[ bTownId ].ubRating / 3));
759 		}
760 	}
761 }
762 
GetNumberOfWholeTownsUnderControl(void)763 INT32 GetNumberOfWholeTownsUnderControl( void )
764 {
765 	INT32 iNumber = 0;
766 	INT8 bTownId = 0;
767 
768 	// run through the list of towns..if the entire town is under player control, then increment the number of towns under player control
769 
770 	// make sure that each town is one for which loyalty matters
771 	for ( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
772 	{
773 		if ( IsTownUnderCompleteControlByPlayer( bTownId ) && gfTownUsesLoyalty[ bTownId ] )
774 		{
775 			iNumber++;
776 		}
777 	}
778 
779 	return( iNumber );
780 }
781 
782 
GetNumberOfWholeTownsUnderControlButExcludeCity(INT8 bCityToExclude)783 INT32 GetNumberOfWholeTownsUnderControlButExcludeCity( INT8 bCityToExclude )
784 {
785 	INT32 iNumber = 0;
786 	INT8 bTownId = 0;
787 
788 	// run through the list of towns..if the entire town is under player control, then increment the number of towns under player control
789 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
790 	{
791 		if ( IsTownUnderCompleteControlByPlayer( bTownId ) && ( bCityToExclude != bTownId ) && gfTownUsesLoyalty[ bTownId ] )
792 		{
793 			iNumber++;
794 		}
795 	}
796 
797 	return( iNumber );
798 }
799 
800 // is the ENTIRE town under player control?
IsTownUnderCompleteControlByPlayer(INT8 bTownId)801 INT32 IsTownUnderCompleteControlByPlayer( INT8 bTownId )
802 {
803 	return GetTownSectorSize(bTownId) == GetTownSectorsUnderControl(bTownId);
804 }
805 
806 
807 // is the ENTIRE town under enemy control?
IsTownUnderCompleteControlByEnemy(INT8 bTownId)808 static INT32 IsTownUnderCompleteControlByEnemy(INT8 bTownId)
809 {
810 	return GetTownSectorsUnderControl(bTownId) == 0;
811 }
812 
AdjustLoyaltyForCivsEatenByMonsters(INT16 sSectorX,INT16 sSectorY,UINT8 ubHowMany)813 void AdjustLoyaltyForCivsEatenByMonsters( INT16 sSectorX, INT16 sSectorY, UINT8 ubHowMany)
814 {
815 	UINT32 uiLoyaltyChange = 0;
816 	ST::string str;
817 	ST::string pSectorString;
818 
819 	UINT8 const bTownId = GetTownIdForSector(SECTOR(sSectorX, sSectorY));
820 
821 	// if NOT in a town
822 	if( bTownId == BLANK_SECTOR )
823 	{
824 		return;
825 	}
826 
827 	//Report this to player
828 	pSectorString = GetSectorIDString(sSectorX, sSectorY, 0, TRUE);
829 	str = st_format_printf(gpStrategicString[ STR_DIALOG_CREATURES_KILL_CIVILIANS ], ubHowMany, pSectorString);
830 	DoScreenIndependantMessageBox( str, MSG_BOX_FLAG_OK, MapScreenDefaultOkBoxCallback );
831 
832 	// use same formula as if it were a civilian "murder" in tactical!!!
833 	uiLoyaltyChange = ubHowMany * BASIC_COST_FOR_CIV_MURDER * MULTIPLIER_FOR_MURDER_BY_MONSTER;
834 	DecrementTownLoyalty( bTownId, uiLoyaltyChange );
835 }
836 
837 
838 // this applies the SAME change to every town equally, regardless of distance from the event
IncrementTownLoyaltyEverywhere(UINT32 uiLoyaltyIncrease)839 void IncrementTownLoyaltyEverywhere( UINT32 uiLoyaltyIncrease )
840 {
841 	INT8 bTownId;
842 
843 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
844 	{
845 		IncrementTownLoyalty( bTownId, uiLoyaltyIncrease );
846 	}
847 }
848 
DecrementTownLoyaltyEverywhere(UINT32 uiLoyaltyDecrease)849 void DecrementTownLoyaltyEverywhere( UINT32 uiLoyaltyDecrease )
850 {
851 	INT8 bTownId;
852 
853 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
854 	{
855 		DecrementTownLoyalty( bTownId, uiLoyaltyDecrease );
856 	}
857 }
858 // this applies the change to every town differently, depending on the distance from the event
HandleGlobalLoyaltyEvent(UINT8 ubEventType,INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)859 void HandleGlobalLoyaltyEvent( UINT8 ubEventType, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ)
860 {
861 	INT32 iLoyaltyChange;
862 	INT8 bTownId = 0;
863 
864 	if( bSectorZ == 0 )
865 	{
866 		// grab town id, if this event occured within one
867 		bTownId = GetTownIdForSector(SECTOR(sSectorX, sSectorY));
868 	}
869 
870 	// should other towns ignore events occuring in this town?
871 	if( bTownId == SAN_MONA )
872 	{
873 		return;
874 	}
875 
876 	// determine what the base loyalty change of this event type is worth
877 	// these are ->hundredths<- of loyalty points, so choose appropriate values accordingly!
878 	// the closer a town is to the event, the more pronounced the effect upon that town is
879 	switch (ubEventType)
880 	{
881 		case GLOBAL_LOYALTY_BATTLE_WON:
882 			CheckConditionsForTriggeringCreatureQuest( sSectorX, sSectorY, bSectorZ );
883 			iLoyaltyChange = 500;
884 			break;
885 		case GLOBAL_LOYALTY_QUEEN_BATTLE_WON:
886 			CheckConditionsForTriggeringCreatureQuest( sSectorX, sSectorY, bSectorZ );
887 			iLoyaltyChange = 1000;
888 			break;
889 		case GLOBAL_LOYALTY_BATTLE_LOST:
890 			iLoyaltyChange = -750;
891 			break;
892 		case GLOBAL_LOYALTY_ENEMY_KILLED:
893 			iLoyaltyChange = 50;
894 			break;
895 		case GLOBAL_LOYALTY_NATIVE_KILLED:
896 			// note that there is special code (much more severe) for murdering civilians in the currently loaded sector.
897 			// this event is intended more for processing militia casualties, and the like
898 			iLoyaltyChange = -50;
899 			break;
900 		case GLOBAL_LOYALTY_ABANDON_MILITIA:
901 			// it doesn't matter how many of them are being abandoned
902 			iLoyaltyChange = -750;
903 			break;
904 		case GLOBAL_LOYALTY_GAIN_TOWN_SECTOR:
905 			iLoyaltyChange = 500;
906 			break;
907 		case GLOBAL_LOYALTY_LOSE_TOWN_SECTOR:
908 			iLoyaltyChange = -1000;
909 			break;
910 		case GLOBAL_LOYALTY_LIBERATE_WHOLE_TOWN:
911 			iLoyaltyChange = 1000;
912 			break;
913 		case GLOBAL_LOYALTY_GAIN_MINE:
914 			iLoyaltyChange = 1000;
915 			break;
916 		case GLOBAL_LOYALTY_LOSE_MINE:
917 			iLoyaltyChange = -1500;
918 			break;
919 		case GLOBAL_LOYALTY_GAIN_SAM:
920 			iLoyaltyChange = 250;
921 			break;
922 		case GLOBAL_LOYALTY_LOSE_SAM:
923 			iLoyaltyChange = -250;
924 			break;
925 
926 		default:
927 			Assert(FALSE);
928 			return;
929 	}
930 
931 	AffectAllTownsLoyaltyByDistanceFrom( iLoyaltyChange, sSectorX, sSectorY, bSectorZ);
932 }
933 
934 
AffectAllTownsLoyaltyByDistanceFrom(INT32 iLoyaltyChange,INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)935 static void AffectAllTownsLoyaltyByDistanceFrom(INT32 iLoyaltyChange, INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ)
936 {
937 	INT16 sEventSector;
938 	INT8 bTownId;
939 	INT32 iThisDistance;
940 	INT32 iShortestDistance[NUM_TOWNS];
941 	INT32 iPercentAdjustment;
942 	INT32 iDistanceAdjustedLoyalty;
943 
944 
945 	// preset shortest distances to high values prior to searching for a minimum
946 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
947 	{
948 		iShortestDistance[ bTownId ] = 999999;
949 	}
950 
951 	sEventSector = sSectorX + ( MAP_WORLD_X * sSectorY );
952 
953 	// need a temporary group create to use for laying down distance paths
954 	GROUP& g = *CreateNewPlayerGroupDepartingFromSector(sSectorX, sSectorY);
955 
956 	// calc distance to the event sector from EACH SECTOR of each town, keeping only the shortest one for every town
957 	FOR_EACH_TOWN_SECTOR(i)
958 	{
959 		bTownId = i->town;
960 
961 		// skip path test if distance is already known to be zero to speed this up a bit
962 		if (iShortestDistance[ bTownId ] > 0 )
963 		{
964 			// calculate across how many sectors the fastest travel path from event to this town sector
965 			iThisDistance = FindStratPath(sEventSector, SECTOR_INFO_TO_STRATEGIC_INDEX(i->sector), g, FALSE);
966 
967 			if (iThisDistance < iShortestDistance[ bTownId ])
968 			{
969 				iShortestDistance[ bTownId ] = iThisDistance;
970 			}
971 		}
972 	}
973 
974 	// must always remove that temporary group!
975 	RemoveGroup(g);
976 
977 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
978 	{
979 		// doesn't affect towns where player hasn't established a "presence" yet
980 		if (!gTownLoyalty[ bTownId ].fStarted)
981 		{
982 			continue;
983 		}
984 
985 		// if event was underground, double effective distance
986 		if (bSectorZ != 0)
987 		{
988 			iShortestDistance[ bTownId ] *= 2;
989 		}
990 
991 		// This number here controls how many sectors away is the "norm" for the unadjusted loyalty change value
992 		if (iShortestDistance[ bTownId ] < LOYALTY_EVENT_DISTANCE_THRESHOLD)
993 		{
994 			// add 25% per sector below the threshold
995 			iPercentAdjustment = 25 * (LOYALTY_EVENT_DISTANCE_THRESHOLD - iShortestDistance[ bTownId ]);
996 		}
997 		else
998 		if (iShortestDistance[ bTownId ] > LOYALTY_EVENT_DISTANCE_THRESHOLD)
999 		{
1000 			// subtract 10% per sector above the threshold
1001 			iPercentAdjustment = -10 * (iShortestDistance[ bTownId ] - LOYALTY_EVENT_DISTANCE_THRESHOLD);
1002 
1003 			// don't allow it to go below -100, that would change the sign of the loyalty effect!
1004 			if (iPercentAdjustment < -100)
1005 			{
1006 				iPercentAdjustment = -100;
1007 			}
1008 		}
1009 		else
1010 		{
1011 			// no distance adjustment necessary
1012 			iPercentAdjustment = 0;
1013 		}
1014 
1015 
1016 		// calculate loyalty affects as adjusted for distance to this town
1017 		iDistanceAdjustedLoyalty = (iLoyaltyChange * (100 + iPercentAdjustment)) / 100;
1018 
1019 		if (iDistanceAdjustedLoyalty == 0)
1020 		{
1021 			// no measurable effect, skip this town
1022 			continue;
1023 		}
1024 
1025 		if ( iDistanceAdjustedLoyalty > 0)
1026 		{
1027 			IncrementTownLoyalty( bTownId, iDistanceAdjustedLoyalty );
1028 		}
1029 		else
1030 		{
1031 			// the decrement amount MUST be positive
1032 			iDistanceAdjustedLoyalty *= -1;
1033 			DecrementTownLoyalty( bTownId, iDistanceAdjustedLoyalty );
1034 		}
1035 	}
1036 
1037 }
1038 
1039 
1040 
1041 // to be called whenever player gains control of a sector in any way
CheckIfEntireTownHasBeenLiberated(INT8 bTownId,INT16 sSectorX,INT16 sSectorY)1042 void CheckIfEntireTownHasBeenLiberated( INT8 bTownId, INT16 sSectorX, INT16 sSectorY )
1043 {
1044 	// the whole town is under our control, check if we never libed this town before
1045 	if ( !gTownLoyalty[ bTownId ].fLiberatedAlready && IsTownUnderCompleteControlByPlayer ( bTownId ) )
1046 	{
1047 		if ( MilitiaTrainingAllowedInSector( sSectorX, sSectorY, 0 ) )
1048 		{
1049 			// give a loyalty bonus
1050 			HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_LIBERATE_WHOLE_TOWN, sSectorX, sSectorY, 0 );
1051 
1052 			// set fact is has been lib'ed and set history event
1053 			AddHistoryToPlayersLog( HISTORY_LIBERATED_TOWN, bTownId, GetWorldTotalMin(), sSectorX, sSectorY );
1054 
1055 			HandleMeanWhileEventPostingForTownLiberation( bTownId );
1056 		}
1057 
1058 		// even taking over non-trainable "towns" like Orta/Tixa for the first time should count as "player activity"
1059 		if ( gGameOptions.ubDifficultyLevel >= DIF_LEVEL_HARD )
1060 		{
1061 			UpdateLastDayOfPlayerActivity( ( UINT16 ) ( GetWorldDay() + 4 ) );
1062 		}
1063 		else
1064 		{
1065 			UpdateLastDayOfPlayerActivity( ( UINT16 ) ( GetWorldDay() + 2 ) );
1066 		}
1067 
1068 		// set flag even for towns where you can't train militia, useful for knowing Orta/Tixa were previously controlled
1069 		gTownLoyalty[ bTownId ].fLiberatedAlready = TRUE;
1070 	}
1071 }
1072 
CheckIfEntireTownHasBeenLost(INT8 bTownId,INT16 sSectorX,INT16 sSectorY)1073 void CheckIfEntireTownHasBeenLost( INT8 bTownId, INT16 sSectorX, INT16 sSectorY )
1074 {
1075 	// NOTE:  only towns which allow you to train militia are important enough to get
1076 	// reported here (and they're the only ones you can protect)
1077 	if ( MilitiaTrainingAllowedInSector( sSectorX, sSectorY, 0 ) && IsTownUnderCompleteControlByEnemy(bTownId) )
1078 	{
1079 		// the whole town is under enemy control, check if we libed this town before
1080 		if ( gTownLoyalty[ bTownId ].fLiberatedAlready )
1081 		{
1082 			HandleMeanWhileEventPostingForTownLoss();
1083 		}
1084 	}
1085 }
1086 
1087 
1088 
1089 
HandleLoyaltyChangeForNPCAction(UINT8 ubNPCProfileId)1090 void HandleLoyaltyChangeForNPCAction( UINT8 ubNPCProfileId )
1091 {
1092 	switch ( ubNPCProfileId )
1093 	{
1094 		case MIGUEL:
1095 			// Omerta loyalty increases when Miguel receives letter from Enrico
1096 			IncrementTownLoyalty( OMERTA, LOYALTY_BONUS_MIGUEL_READS_LETTER );
1097 			break;
1098 
1099 		case DOREEN:
1100 			// having freed the child labourers... she is releasing them herself!
1101 			IncrementTownLoyalty( DRASSEN, LOYALTY_BONUS_CHILDREN_FREED_DOREEN_SPARED );
1102 			break;
1103 
1104 		case MARTHA:
1105 			// if Joey is still alive
1106 			if ( gMercProfiles[ JOEY ].bMercStatus != MERC_IS_DEAD )
1107 			{
1108 				IncrementTownLoyalty( CAMBRIA, LOYALTY_BONUS_MARTHA_WHEN_JOEY_RESCUED );
1109 			}
1110 			break;
1111 
1112 		case KEITH:
1113 			// Hillbilly problem solved
1114 			IncrementTownLoyalty( CAMBRIA, LOYALTY_BONUS_KEITH_WHEN_HILLBILLY_SOLVED );
1115 			break;
1116 
1117 		case YANNI:
1118 			// Chalice of Chance returned to Chitzena
1119 			IncrementTownLoyalty( CHITZENA, LOYALTY_BONUS_YANNI_WHEN_CHALICE_RETURNED_LOCAL );
1120 			// NOTE: This affects Chitzena,too, a second time, so first value is discounted for it
1121 			IncrementTownLoyaltyEverywhere( LOYALTY_BONUS_YANNI_WHEN_CHALICE_RETURNED_GLOBAL );
1122 			break;
1123 
1124 		case AUNTIE:
1125 			// Bloodcats killed
1126 			IncrementTownLoyalty( ALMA, LOYALTY_BONUS_AUNTIE_WHEN_BLOODCATS_KILLED );
1127 			break;
1128 
1129 		case MATT:
1130 			// Brother Dynamo freed
1131 			IncrementTownLoyalty( ALMA, LOYALTY_BONUS_MATT_WHEN_DYNAMO_FREED );
1132 			break;
1133 
1134 	}
1135 }
1136 
1137 
1138 
1139 // set the location of the first encounter with enemy
SetTheFirstBattleSector(INT16 sSectorValue)1140 void SetTheFirstBattleSector( INT16 sSectorValue )
1141 {
1142 	if( sWorldSectorLocationOfFirstBattle == 0 )
1143 	{
1144 		sWorldSectorLocationOfFirstBattle = sSectorValue;
1145 	}
1146 }
1147 
1148 
1149 // Did first battle take place here?
DidFirstBattleTakePlaceInThisTown(INT8 const town)1150 bool DidFirstBattleTakePlaceInThisTown(INT8 const town)
1151 {
1152 	return GetTownIdForSector(STRATEGIC_INDEX_TO_SECTOR_INFO(sWorldSectorLocationOfFirstBattle)) == town;
1153 }
1154 
1155 
PlayerStrength(void)1156 static UINT32 PlayerStrength(void)
1157 {
1158 	UINT32 uiStrength, uiTotal = 0;
1159 
1160 	CFOR_EACH_IN_TEAM(s, OUR_TEAM)
1161 	{
1162 		if (s->bInSector ||
1163 			(
1164 				s->fBetweenSectors &&
1165 				s->ubPrevSectorID % 16 + 1 == gWorldSectorX &&
1166 				s->ubPrevSectorID / 16 + 1 == gWorldSectorY &&
1167 				s->bSectorZ == gbWorldSectorZ
1168 			))
1169 		{
1170 			// count this person's strength (condition), calculated as life reduced up to half according to maxbreath
1171 			uiStrength = s->bLife * (s->bBreathMax + 100) / 200;
1172 			uiTotal += uiStrength;
1173 		}
1174 	}
1175 	return( uiTotal );
1176 }
1177 
1178 
EnemyStrength(void)1179 static UINT32 EnemyStrength(void)
1180 {
1181 	UINT32 strength = 0;
1182 	CFOR_EACH_NON_PLAYER_SOLDIER(s)
1183 	{
1184 		if (!s->bInSector || s->bNeutral) continue;
1185 		/* count this person's strength (condition), calculated as life reduced up
1186 		 * to half according to maxbreath */
1187 		strength += s->bLife * (s->bBreathMax + 100) / 200;
1188 	}
1189 	return strength;
1190 }
1191 
1192 
1193 //Function assumes that mercs have retreated already.  Handles two cases, one for general merc retreat
1194 //which slightly demoralizes the mercs, the other handles abandonment of militia forces which poses
1195 //as a serious loyalty penalty.
HandleLoyaltyImplicationsOfMercRetreat(INT8 bRetreatCode,INT16 sSectorX,INT16 sSectorY,INT16 sSectorZ)1196 void HandleLoyaltyImplicationsOfMercRetreat( INT8 bRetreatCode, INT16 sSectorX, INT16 sSectorY, INT16 sSectorZ )
1197 {
1198 	if( CountAllMilitiaInSector( sSectorX, sSectorY ) )
1199 	{ //Big morale penalty!
1200 		HandleGlobalLoyaltyEvent( GLOBAL_LOYALTY_ABANDON_MILITIA, sSectorX, sSectorY, (INT8)sSectorZ );
1201 	}
1202 
1203 	//Standard retreat penalty
1204 	if ( bRetreatCode == RETREAT_TACTICAL_TRAVERSAL )
1205 	{
1206 		// if not worse than 2:1 odds, then penalize morale
1207 		if ( gTacticalStatus.fEnemyInSector && ( PlayerStrength() * 2 >= EnemyStrength() ) )
1208 		{
1209 			HandleMoraleEvent( NULL, MORALE_RAN_AWAY, sSectorX, sSectorY, (INT8)sSectorZ );
1210 		}
1211 	}
1212 	else
1213 	{
1214 		HandleMoraleEvent( NULL, MORALE_RAN_AWAY, sSectorX, sSectorY, (INT8)sSectorZ );
1215 	}
1216 }
1217 
1218 
MaximizeLoyaltyForDeidrannaKilled(void)1219 void MaximizeLoyaltyForDeidrannaKilled(void)
1220 {
1221 	INT8 bTownId;
1222 
1223 	// max out loyalty to player all across Arulco
1224 	for( bTownId = FIRST_TOWN; bTownId < NUM_TOWNS; bTownId++ )
1225 	{
1226 		// it's possible one of the towns still has creature problems, but it's too much of a pain to worry about it now
1227 		SetTownLoyalty(bTownId, 100);
1228 	}
1229 }
1230