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