1 #include "Strategic_Mines.h"
2
3 #include "Campaign_Types.h"
4 #include "ContentManager.h"
5 #include "Creature_Spreading.h"
6 #include "Debug.h"
7 #include "Dialogue_Control.h"
8 #include "FileMan.h"
9 #include "Finances.h"
10 #include "Font_Control.h"
11 #include "GameInstance.h"
12 #include "GameSettings.h"
13 #include "Game_Clock.h"
14 #include "Game_Event_Hook.h"
15 #include "History.h"
16 #include "LoadSaveData.h"
17 #include "MapScreen.h"
18 #include "Map_Screen_Interface.h"
19 #include "Message.h"
20 #include "MineModel.h"
21 #include "Quests.h"
22 #include "Random.h"
23 #include "Soldier_Profile.h"
24 #include "StrategicMap.h"
25 #include "Strategic_AI.h"
26 #include "Strategic_Town_Loyalty.h"
27 #include "Text.h"
28 #include "UILayout.h"
29
30
31 // this .cc file handles the strategic level of mines and income from them
32
33 #define REMOVAL_RATE_INCREMENT 250 // the smallest increment by which removal rate change during depletion (use round #s)
34
35 #define LOW_MINE_LOYALTY_THRESHOLD 50 // below this the head miner considers his town's population disloyal
36
37 // Mine production is being processed 4x daily: 9am ,noon, 3pm, and 6pm.
38 // This is loosely based on a 6am-6pm working day of 4 "shifts".
39 #define MINE_PRODUCTION_NUMBER_OF_PERIODS 4 // how many times a day mine production is processed
40 #define MINE_PRODUCTION_START_TIME (9 * 60) // hour of first daily mine production event (in minutes)
41 #define MINE_PRODUCTION_PERIOD (3 * 60) // time seperating daily mine production events (in minutes)
42 #define MINE_PRODUCTION_DAYS_AFTER_HEAD_MINER_WARNING 2.5 // how many more days the mine can still produce after head miner warns of depletion
43
44
45 // this table holds mine values that change during the course of the game and must be saved
46 std::vector<MINE_STATUS_TYPE> gMineStatus;
47
48 struct HEAD_MINER_TYPE
49 {
50 UINT16 usProfileId;
51 INT8 bQuoteNum[NUM_HEAD_MINER_STRATEGIC_QUOTES];
52 };
53
54
55 static const HEAD_MINER_TYPE gHeadMinerData[NUM_HEAD_MINERS] =
56 {
57 // Profile # running out creatures! all dead! creatures again!
58 { FRED, { 17, 18, 27, 26 } },
59 { MATT, { -1, 18, 32, 31 } },
60 { OSWALD, { 14, 15, 24, 23 } },
61 { CALVIN, { 14, 15, 24, 23 } },
62 { CARL, { 14, 15, 24, 23 } }
63 };
64
65
InitializeMines(void)66 void InitializeMines( void )
67 {
68 UINT8 ubMineIndex;
69 MINE_STATUS_TYPE *pMineStatus;
70 UINT8 ubMineProductionIncreases;
71 UINT8 ubDepletedMineIndex;
72 UINT8 ubMinDaysBeforeDepletion = 20;
73
74
75 // set up initial mine statu
76 gMineStatus.clear();
77 for (auto mine : GCM->getMines())
78 {
79 MINE_STATUS_TYPE pMineStatus{};
80
81 pMineStatus.ubMineType = mine->mineType;
82 pMineStatus.uiMaxRemovalRate = mine->minimumMineProduction;
83 pMineStatus.fEmpty = (pMineStatus.uiMaxRemovalRate == 0) ? TRUE : FALSE;
84 gMineStatus.push_back(pMineStatus);
85 }
86
87 // randomize the exact size each mine. The total production is always the same and depends on the game difficulty,
88 // but some mines will produce more in one game than another, while others produce less
89
90 // adjust for game difficulty
91 switch( gGameOptions.ubDifficultyLevel )
92 {
93 case DIF_LEVEL_EASY:
94 case DIF_LEVEL_MEDIUM:
95 ubMineProductionIncreases = 25;
96 break;
97 case DIF_LEVEL_HARD:
98 ubMineProductionIncreases = 20;
99 break;
100 default:
101 SLOGA("Invalid Difficulty level");
102 return;
103 }
104
105 auto minesData = GCM->getMines();
106 while (ubMineProductionIncreases > 0)
107 {
108 // pick a producing mine at random and increase its production
109 do
110 {
111 ubMineIndex = ( UINT8 ) Random(minesData.size());
112 } while (gMineStatus[ubMineIndex].fEmpty);
113
114 // increase mine production by 20% of the base (minimum) rate
115 gMineStatus[ubMineIndex].uiMaxRemovalRate += (minesData[ubMineIndex]->minimumMineProduction / 5);
116
117 ubMineProductionIncreases--;
118 }
119
120
121 // choose which mine will run out of production. This will never be the Alma mine or an empty mine (San Mona)...
122 do
123 {
124 ubDepletedMineIndex = ( UINT8 ) Random(minesData.size());
125 // Try next one if this mine can't run out for quest-related reasons (see Ian)
126 } while (gMineStatus[ubDepletedMineIndex].fEmpty || minesData[ubDepletedMineIndex]->noDepletion);
127
128
129 for( ubMineIndex = 0; ubMineIndex < gMineStatus.size(); ubMineIndex++ )
130 {
131 pMineStatus = &(gMineStatus[ ubMineIndex ]);
132
133 if (ubMineIndex == ubDepletedMineIndex)
134 {
135 if (minesData[ubDepletedMineIndex]->delayDepletion)
136 {
137 ubMinDaysBeforeDepletion = 20;
138 }
139 else
140 {
141 ubMinDaysBeforeDepletion = 10;
142 }
143
144 // the mine that runs out has only enough ore for this many days of full production
145 pMineStatus->uiRemainingOreSupply = ubMinDaysBeforeDepletion * (MINE_PRODUCTION_NUMBER_OF_PERIODS * pMineStatus->uiMaxRemovalRate);
146
147 // ore starts running out when reserves drop to less than 2.5 days worth of supply
148 pMineStatus->uiOreRunningOutPoint = (UINT32)(MINE_PRODUCTION_DAYS_AFTER_HEAD_MINER_WARNING * MINE_PRODUCTION_NUMBER_OF_PERIODS * pMineStatus->uiMaxRemovalRate);
149 }
150 else
151 if (!pMineStatus->fEmpty)
152 {
153 // never runs out...
154 pMineStatus->uiRemainingOreSupply = 999999999; // essentially unlimited
155 pMineStatus->uiOreRunningOutPoint = 0;
156 }
157 else
158 {
159 // already empty
160 pMineStatus->uiRemainingOreSupply = 0;
161 pMineStatus->uiOreRunningOutPoint = 0;
162 }
163 }
164 }
165
166
HourlyMinesUpdate(void)167 void HourlyMinesUpdate(void)
168 {
169 UINT8 ubMineIndex;
170 MINE_STATUS_TYPE *pMineStatus;
171
172 // check every non-empty mine
173 for( ubMineIndex = 0; ubMineIndex < gMineStatus.size(); ubMineIndex++ )
174 {
175 pMineStatus = &(gMineStatus[ ubMineIndex ]);
176
177 if (pMineStatus->fEmpty)
178 {
179 // nobody is working that mine, so who cares
180 continue;
181 }
182
183 // check if the mine has any monster creatures in it
184 if (MineClearOfMonsters( ubMineIndex ))
185 {
186 // if it's shutdown, but not permanently
187 if (IsMineShutDown( ubMineIndex ) && !pMineStatus->fShutDownIsPermanent)
188 {
189 // if we control production in it
190 if (PlayerControlsMine( ubMineIndex ))
191 {
192 IssueHeadMinerQuote( ubMineIndex, HEAD_MINER_STRATEGIC_QUOTE_CREATURES_GONE );
193 }
194
195 // put mine back in service
196 RestartMineProduction( ubMineIndex );
197 }
198 }
199 else // mine is monster infested
200 {
201 // 'Der be monsters crawling around in there, lad!!!
202
203 // if it's still producing
204 if (!IsMineShutDown( ubMineIndex ))
205 {
206 // gotta put a stop to that!
207
208 // if we control production in it
209 if (PlayerControlsMine( ubMineIndex ))
210 {
211 // 2 different quotes, depends whether or not it's the first time this has happened
212 HeadMinerQuote ubQuoteType;
213 if (pMineStatus->fPrevInvadedByMonsters)
214 {
215 ubQuoteType = HEAD_MINER_STRATEGIC_QUOTE_CREATURES_AGAIN;
216 }
217 else
218 {
219 ubQuoteType = HEAD_MINER_STRATEGIC_QUOTE_CREATURES_ATTACK;
220 pMineStatus->fPrevInvadedByMonsters = TRUE;
221
222 if ( gubQuest[ QUEST_CREATURES ] == QUESTNOTSTARTED )
223 {
224 // start it now!
225 UINT8 const sector = GCM->getMine(ubMineIndex)->entranceSector;
226 StartQuest(QUEST_CREATURES, SECTORX(sector), SECTORY(sector));
227 }
228 }
229
230 // tell player the good news...
231 IssueHeadMinerQuote( ubMineIndex, ubQuoteType );
232 }
233
234 // and immediately halt all work at the mine (whether it's ours or the queen's). This is a temporary shutdown
235 ShutOffMineProduction( ubMineIndex );
236 }
237 }
238 }
239 }
240
241
GetTotalLeftInMine(UINT8 ubMineIndex)242 INT32 GetTotalLeftInMine( UINT8 ubMineIndex )
243 {
244 // returns the value of the mine
245
246 Assert(ubMineIndex < gMineStatus.size());
247
248 return ( gMineStatus[ ubMineIndex ].uiRemainingOreSupply );
249 }
250
251
GetMaxPeriodicRemovalFromMine(UINT8 ubMineIndex)252 UINT32 GetMaxPeriodicRemovalFromMine( UINT8 ubMineIndex )
253 {
254 // returns max amount that can be mined in a time period
255
256 Assert(ubMineIndex < gMineStatus.size());
257
258 // if mine is shut down
259 if ( gMineStatus[ ubMineIndex ].fShutDown)
260 {
261 return ( 0 );
262 }
263
264 return( gMineStatus[ ubMineIndex ].uiMaxRemovalRate );
265 }
266
267
GetMaxDailyRemovalFromMine(UINT8 const mine_id)268 UINT32 GetMaxDailyRemovalFromMine(UINT8 const mine_id)
269 {
270 Assert(mine_id < gMineStatus.size());
271 MINE_STATUS_TYPE const& m = gMineStatus[mine_id];
272
273 if (m.fShutDown) return 0;
274
275 UINT32 const rate = MINE_PRODUCTION_NUMBER_OF_PERIODS * m.uiMaxRemovalRate;
276 UINT32 const remaining = m.uiRemainingOreSupply;
277 return rate < remaining ? rate : remaining;
278 }
279
280
GetTownAssociatedWithMine(UINT8 ubMineIndex)281 INT8 GetTownAssociatedWithMine( UINT8 ubMineIndex )
282 {
283 Assert(ubMineIndex < GCM->getMines().size());
284 return GCM->getMine(ubMineIndex)->associatedTownId;
285 }
286
287
AddMineHistoryEvent(UINT8 const event,UINT const mine_id)288 static void AddMineHistoryEvent(UINT8 const event, UINT const mine_id)
289 {
290 auto m = GCM->getMine(mine_id);
291 AddHistoryToPlayersLog(event, m->associatedTownId, GetWorldTotalMin(), SECTORX(m->entranceSector), SECTORY(m->entranceSector));
292 }
293
294
295 // remove actual ore from mine
ExtractOreFromMine(UINT8 ubMineIndex,UINT32 uiAmount)296 static UINT32 ExtractOreFromMine(UINT8 ubMineIndex, UINT32 uiAmount)
297 {
298 // will remove the ore from the mine and return the amount that was removed
299 UINT32 uiAmountExtracted = 0;
300
301 Assert(ubMineIndex < gMineStatus.size());
302
303 // if mine is shut down
304 if ( gMineStatus[ ubMineIndex ].fShutDown)
305 {
306 return ( 0 );
307 }
308
309 // if not capable of extracting anything, bail now
310 if (uiAmount == 0)
311 {
312 return ( 0 );
313 }
314
315 // will this exhaust the ore in this mine?
316 if( uiAmount >= gMineStatus[ ubMineIndex ].uiRemainingOreSupply )
317 {
318 // exhaust remaining ore
319 uiAmountExtracted = gMineStatus[ ubMineIndex ].uiRemainingOreSupply;
320 gMineStatus[ ubMineIndex ].uiRemainingOreSupply = 0;
321 gMineStatus[ ubMineIndex ].uiMaxRemovalRate = 0;
322 gMineStatus[ ubMineIndex ].fEmpty = TRUE;
323 gMineStatus[ ubMineIndex ].fRunningOut = FALSE;
324
325 // tell the strategic AI about this, that mine's and town's value is greatly reduced
326 StrategicHandleMineThatRanOut(GetMineSector(ubMineIndex));
327
328 AddMineHistoryEvent(HISTORY_MINE_RAN_OUT, ubMineIndex);
329 }
330 else // still some left after this extraction
331 {
332 // set amount used, and decrement ore remaining in mine
333 uiAmountExtracted = uiAmount;
334 gMineStatus[ ubMineIndex ].uiRemainingOreSupply -= uiAmount;
335
336 // one of the mines (randomly chosen) will start running out eventually, check if we're there yet
337 if (gMineStatus[ ubMineIndex ].uiRemainingOreSupply < gMineStatus[ ubMineIndex ].uiOreRunningOutPoint)
338 {
339 gMineStatus[ ubMineIndex ].fRunningOut = TRUE;
340
341 // round all fractions UP to the next REMOVAL_RATE_INCREMENT
342 gMineStatus[ ubMineIndex ].uiMaxRemovalRate = (UINT32) (((FLOAT) gMineStatus[ ubMineIndex ].uiRemainingOreSupply / 10) / REMOVAL_RATE_INCREMENT + 0.9999) * REMOVAL_RATE_INCREMENT;
343
344
345 // if we control it
346 if (PlayerControlsMine(ubMineIndex))
347 {
348 // and haven't yet been warned that it's running out
349 if (!gMineStatus[ ubMineIndex ].fWarnedOfRunningOut)
350 {
351 // that mine's head miner tells player that the mine is running out
352 IssueHeadMinerQuote( ubMineIndex, HEAD_MINER_STRATEGIC_QUOTE_RUNNING_OUT );
353 gMineStatus[ ubMineIndex ].fWarnedOfRunningOut = TRUE;
354 AddMineHistoryEvent(HISTORY_MINE_RUNNING_OUT, ubMineIndex);
355 }
356 }
357 }
358 }
359
360 return( uiAmountExtracted );
361 }
362
363
364 // Get the available player workforce for the mine [0,100]
GetAvailableWorkForceForMineForPlayer(UINT8 ubMineIndex)365 static INT32 GetAvailableWorkForceForMineForPlayer(UINT8 ubMineIndex)
366 {
367 // look for available workforce in the town associated with the mine
368 INT32 iWorkForceSize = 0;
369 INT8 bTownId = 0;
370
371 // return the loyalty of the town associated with the mine
372
373 Assert(ubMineIndex < gMineStatus.size());
374 auto mine = GCM->getMine(ubMineIndex);
375
376 // if mine is shut down
377 if ( gMineStatus[ ubMineIndex ].fShutDown)
378 {
379 return ( 0 );
380 }
381
382 // until the player contacts the head miner, production in mine ceases if in player's control
383 if ( !gMineStatus[ ubMineIndex ].fSpokeToHeadMiner)
384 {
385 return ( 0 );
386 }
387
388
389 bTownId = mine->associatedTownId;
390
391 UINT8 numSectors = GetTownSectorSize( bTownId );
392 Assert(numSectors > 0);
393 UINT8 numSectorsUnderControl = GetTownSectorsUnderControl( bTownId );
394 Assert(numSectorsUnderControl <= numSectors);
395
396 // get workforce size (is 0-100 based on local town's loyalty)
397 iWorkForceSize = gTownLoyalty[ bTownId ].ubRating;
398
399 // now adjust for town size.. the number of sectors you control
400 iWorkForceSize = iWorkForceSize * numSectorsUnderControl / numSectors;
401
402 return ( iWorkForceSize );
403 }
404
405
406 // get workforce conscripted by enemy for mine
GetAvailableWorkForceForMineForEnemy(UINT8 ubMineIndex)407 static INT32 GetAvailableWorkForceForMineForEnemy(UINT8 ubMineIndex)
408 {
409 // look for available workforce in the town associated with the mine
410 INT32 iWorkForceSize = 0;
411 INT8 bTownId = 0;
412
413 // return the loyalty of the town associated with the mine
414
415 Assert(ubMineIndex < GCM->getMines().size());
416 auto mine = GCM->getMine(ubMineIndex);
417
418 // if mine is shut down
419 if ( gMineStatus[ ubMineIndex ].fShutDown)
420 {
421 return ( 0 );
422 }
423
424 bTownId = mine->associatedTownId;
425
426 UINT8 numSectors = GetTownSectorSize( bTownId );
427 Assert(numSectors > 0);
428 UINT8 numSectorsUnderControl = GetTownSectorsUnderControl( bTownId );
429 Assert(numSectorsUnderControl <= numSectors);
430
431 // get workforce size (is 0-100 based on REVERSE of local town's loyalty)
432 iWorkForceSize = 100 - gTownLoyalty[ bTownId ].ubRating;
433
434 // now adjust for town size.. the number of sectors you control
435 iWorkForceSize = iWorkForceSize * (numSectors - numSectorsUnderControl) / numSectors;
436
437 return ( iWorkForceSize );
438 }
439
440
441 // how fast is the mine's workforce working for you?
GetCurrentWorkRateOfMineForPlayer(UINT8 ubMineIndex)442 static INT32 GetCurrentWorkRateOfMineForPlayer(UINT8 ubMineIndex)
443 {
444 INT32 iWorkRate = 0;
445
446 // multiply maximum possible removal rate by the percentage of workforce currently working
447 iWorkRate = (gMineStatus[ ubMineIndex ].uiMaxRemovalRate * GetAvailableWorkForceForMineForPlayer( ubMineIndex )) / 100;
448
449 return( iWorkRate );
450 }
451
452
453 // how fast is workforce working for the enemy
GetCurrentWorkRateOfMineForEnemy(UINT8 ubMineIndex)454 static INT32 GetCurrentWorkRateOfMineForEnemy(UINT8 ubMineIndex)
455 {
456 INT32 iWorkRate = 0;
457
458 // multiply maximum possible removal rate by the percentage of workforce currently working
459 iWorkRate = (gMineStatus[ ubMineIndex ].uiMaxRemovalRate * GetAvailableWorkForceForMineForEnemy( ubMineIndex )) / 100;
460
461 return( iWorkRate );
462 }
463
464
465 // mine this mine
MineAMine(UINT8 ubMineIndex)466 static INT32 MineAMine(UINT8 ubMineIndex)
467 {
468 // will extract ore based on available workforce, and increment players income based on amount
469 INT32 iAmtExtracted = 0;
470
471
472 Assert(ubMineIndex < gMineStatus.size());
473
474 // is mine is empty
475 if( gMineStatus[ ubMineIndex ].fEmpty)
476 {
477 return 0;
478 }
479
480 // if mine is shut down
481 if ( gMineStatus[ ubMineIndex ].fShutDown)
482 {
483 return 0;
484 }
485
486
487 // who controls the PRODUCTION in the mine ? (Queen receives production unless player has spoken to the head miner)
488 if( PlayerControlsMine(ubMineIndex) )
489 {
490 // player controlled
491 iAmtExtracted = ExtractOreFromMine( ubMineIndex , GetCurrentWorkRateOfMineForPlayer( ubMineIndex ) );
492
493 // SHOW ME THE MONEY!!!!
494 if( iAmtExtracted > 0 )
495 {
496 // debug message
497 STLOGD("{} - Mine income from {} = ${}", WORLDTIMESTR, GCM->getTownName(GetTownAssociatedWithMine(ubMineIndex)), iAmtExtracted);
498
499 // if this is the first time this mine has produced income for the player in the game
500 if ( !gMineStatus[ ubMineIndex ].fMineHasProducedForPlayer )
501 {
502 // remember that we've earned income from this mine during the game
503 gMineStatus[ ubMineIndex ].fMineHasProducedForPlayer = TRUE;
504 // and when we started to do so...
505 gMineStatus[ ubMineIndex ].uiTimePlayerProductionStarted = GetWorldTotalMin();
506 }
507 }
508 }
509 else // queen controlled
510 {
511 // we didn't want mines to run out without player ever even going to them, so now the queen doesn't reduce the
512 // amount remaining until the mine has produced for the player first (so she'd have to capture it).
513 if ( gMineStatus[ ubMineIndex ].fMineHasProducedForPlayer )
514 {
515 // don't actually give her money, just take production away
516 iAmtExtracted = ExtractOreFromMine( ubMineIndex , GetCurrentWorkRateOfMineForEnemy( ubMineIndex ) );
517 }
518 }
519
520
521 return iAmtExtracted;
522 }
523
524
PostEventsForMineProduction(void)525 void PostEventsForMineProduction(void)
526 {
527 UINT8 ubShift;
528
529 for (ubShift = 0; ubShift < MINE_PRODUCTION_NUMBER_OF_PERIODS; ubShift++)
530 {
531 AddStrategicEvent( EVENT_HANDLE_MINE_INCOME, GetWorldDayInMinutes() + MINE_PRODUCTION_START_TIME + (ubShift * MINE_PRODUCTION_PERIOD), 0 );
532 }
533 }
534
535
HandleIncomeFromMines(void)536 void HandleIncomeFromMines( void )
537 {
538 INT32 iIncome = 0;
539 // mine each mine, check if we own it and such
540 for (UINT8 ubCounter = 0; ubCounter < gMineStatus.size(); ++ubCounter)
541 {
542 // mine this mine
543 iIncome += MineAMine(ubCounter);
544 }
545 if (iIncome)
546 {
547 AddTransactionToPlayersBook(DEPOSIT_FROM_SILVER_MINE, 0, GetWorldTotalMin(), iIncome);
548 }
549 }
550
551
PredictDailyIncomeFromAMine(INT8 const mine_id)552 UINT32 PredictDailyIncomeFromAMine(INT8 const mine_id)
553 {
554 /* Predict income from this mine, estimate assumes mining situation will not
555 * change during next 4 income periods (miner loyalty, % town controlled,
556 * monster infestation level, and current max removal rate may all in fact
557 * change) */
558 if (!PlayerControlsMine(mine_id)) return 0;
559
560 /* Get daily income for this mine (regardless of what time of day it currently
561 * is) */
562 UINT32 const amount = MINE_PRODUCTION_NUMBER_OF_PERIODS * GetCurrentWorkRateOfMineForPlayer(mine_id);
563 UINT32 const remaining = gMineStatus[mine_id].uiRemainingOreSupply;
564 return amount < remaining ? amount : remaining;
565 }
566
567
PredictIncomeFromPlayerMines(void)568 INT32 PredictIncomeFromPlayerMines( void )
569 {
570 INT32 iTotal = 0;
571 UINT8 ubCounter = 0;
572
573 for( ubCounter = 0; ubCounter < gMineStatus.size(); ubCounter++ )
574 {
575 // add up the total
576 iTotal += PredictDailyIncomeFromAMine( ubCounter );
577 }
578
579 return( iTotal );
580 }
581
582
CalcMaxPlayerIncomeFromMines()583 INT32 CalcMaxPlayerIncomeFromMines()
584 {
585 INT32 total = 0;
586 for (MINE_STATUS_TYPE i : gMineStatus)
587 {
588 total += MINE_PRODUCTION_NUMBER_OF_PERIODS * i.uiMaxRemovalRate;
589 }
590 return total;
591 }
592
593
GetMineIndexForSector(UINT8 const sector)594 INT8 GetMineIndexForSector(UINT8 const sector)
595 {
596 return GetIdOfMineForSector(SECTORX(sector), SECTORY(sector), 0);
597 }
598
599
GetMineSector(UINT8 const ubMineIndex)600 UINT8 GetMineSector(UINT8 const ubMineIndex)
601 {
602 Assert(ubMineIndex < gMineStatus.size());
603 return GCM->getMine(ubMineIndex)->entranceSector;
604 }
605
606
607 // get the Strategic Index for the mine associated with this town
GetMineSectorForTown(INT8 const town_id)608 INT16 GetMineSectorForTown(INT8 const town_id)
609 {
610 for (auto m : GCM->getMines())
611 {
612 if (m->associatedTownId != town_id) continue;
613 return SECTOR_INFO_TO_STRATEGIC_INDEX(m->entranceSector);
614 }
615 return -1;
616 }
617
618
PlayerControlsMine(INT8 const mine_id)619 bool PlayerControlsMine(INT8 const mine_id)
620 {
621 auto mine = GCM->getMine(mine_id);
622 return
623 !StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(mine->entranceSector)].fEnemyControlled &&
624 /* Player only controls the actual mine after he has made arrangements to do
625 * so with the head miner there. */
626 gMineStatus[mine_id].fSpokeToHeadMiner;
627 }
628
629
SaveMineStatusToSaveGameFile(HWFILE const f)630 void SaveMineStatusToSaveGameFile(HWFILE const f)
631 {
632 for (MINE_STATUS_TYPE i : gMineStatus)
633 {
634 BYTE data[44];
635 DataWriter d{data};
636 INJ_U8( d, i.ubMineType)
637 INJ_SKIP(d, 3)
638 INJ_U32( d, i.uiMaxRemovalRate)
639 INJ_U32( d, i.uiRemainingOreSupply)
640 INJ_U32( d, i.uiOreRunningOutPoint)
641 INJ_BOOL(d, i.fEmpty)
642 INJ_BOOL(d, i.fRunningOut)
643 INJ_BOOL(d, i.fWarnedOfRunningOut)
644 INJ_BOOL(d, i.fShutDownIsPermanent)
645 INJ_BOOL(d, i.fShutDown)
646 INJ_BOOL(d, i.fPrevInvadedByMonsters)
647 INJ_BOOL(d, i.fSpokeToHeadMiner)
648 INJ_BOOL(d, i.fMineHasProducedForPlayer)
649 INJ_BOOL(d, i.fQueenRetookProducingMine)
650 INJ_BOOL(d, i.fAttackedHeadMiner)
651 INJ_SKIP(d, 2)
652 INJ_U32( d, i.uiTimePlayerProductionStarted)
653 INJ_SKIP(d, 12)
654 Assert(d.getConsumed() == lengthof(data));
655
656 FileWrite(f, data, sizeof(data));
657 }
658 }
659
660
LoadMineStatusFromSavedGameFile(HWFILE const f)661 void LoadMineStatusFromSavedGameFile(HWFILE const f)
662 {
663 // Save game breaks if the number of mines changes, as we do not
664 // store the number of mines when the game was saved.
665 gMineStatus.resize(GCM->getMines().size());
666 for (auto& i : gMineStatus)
667 {
668 BYTE data[44];
669 FileRead(f, data, sizeof(data));
670
671 DataReader d{data};
672 EXTR_U8( d, i.ubMineType)
673 EXTR_SKIP(d, 3)
674 EXTR_U32( d, i.uiMaxRemovalRate)
675 EXTR_U32( d, i.uiRemainingOreSupply)
676 EXTR_U32( d, i.uiOreRunningOutPoint)
677 EXTR_BOOL(d, i.fEmpty)
678 EXTR_BOOL(d, i.fRunningOut)
679 EXTR_BOOL(d, i.fWarnedOfRunningOut)
680 EXTR_BOOL(d, i.fShutDownIsPermanent)
681 EXTR_BOOL(d, i.fShutDown)
682 EXTR_BOOL(d, i.fPrevInvadedByMonsters)
683 EXTR_BOOL(d, i.fSpokeToHeadMiner)
684 EXTR_BOOL(d, i.fMineHasProducedForPlayer)
685 EXTR_BOOL(d, i.fQueenRetookProducingMine)
686 EXTR_BOOL(d, i.fAttackedHeadMiner)
687 EXTR_SKIP(d, 2)
688 EXTR_U32( d, i.uiTimePlayerProductionStarted)
689 EXTR_SKIP(d, 12)
690 Assert(d.getConsumed() == lengthof(data));
691 }
692 }
693
694
ShutOffMineProduction(UINT8 ubMineIndex)695 void ShutOffMineProduction( UINT8 ubMineIndex )
696 {
697 Assert(ubMineIndex < gMineStatus.size());
698
699 if ( !gMineStatus[ ubMineIndex ].fShutDown )
700 {
701 gMineStatus[ ubMineIndex ].fShutDown = TRUE;
702 AddMineHistoryEvent(HISTORY_MINE_SHUTDOWN, ubMineIndex);
703 }
704 }
705
706
RestartMineProduction(UINT8 ubMineIndex)707 void RestartMineProduction( UINT8 ubMineIndex )
708 {
709 Assert(ubMineIndex < gMineStatus.size());
710
711 if ( !gMineStatus[ ubMineIndex ].fShutDownIsPermanent )
712 {
713 if ( gMineStatus[ ubMineIndex ].fShutDown )
714 {
715 gMineStatus[ ubMineIndex ].fShutDown = FALSE;
716 AddMineHistoryEvent(HISTORY_MINE_REOPENED, ubMineIndex);
717 }
718 }
719 }
720
721
MineShutdownIsPermanent(UINT8 ubMineIndex)722 static void MineShutdownIsPermanent(UINT8 ubMineIndex)
723 {
724 Assert(ubMineIndex < gMineStatus.size());
725
726 gMineStatus[ ubMineIndex ].fShutDownIsPermanent = TRUE;
727 }
728
729
IsMineShutDown(UINT8 ubMineIndex)730 BOOLEAN IsMineShutDown( UINT8 ubMineIndex )
731 {
732 Assert(ubMineIndex < gMineStatus.size());
733
734 return(gMineStatus[ ubMineIndex ].fShutDown);
735 }
736
737
GetHeadMinerIndexForMine(UINT8 ubMineIndex)738 static UINT8 GetHeadMinerIndexForMine(UINT8 ubMineIndex)
739 {
740 UINT8 ubMinerIndex = 0;
741 UINT16 usProfileId = 0;
742
743 Assert(ubMineIndex < GCM->getMines().size());
744 auto mine = GCM->getMine(ubMineIndex);
745
746 // loop through all head miners, checking which town they're associated with, looking for one that matches this mine
747 for (ubMinerIndex = 0; ubMinerIndex < NUM_HEAD_MINERS; ubMinerIndex++)
748 {
749 usProfileId = gHeadMinerData[ ubMinerIndex ].usProfileId;
750
751 if (gMercProfiles[ usProfileId ].bTown == mine->associatedTownId)
752 {
753 return(ubMinerIndex);
754 }
755 }
756
757 // not found - yack!
758 Assert( FALSE );
759 return( 0 );
760 }
761
762
IssueHeadMinerQuote(UINT8 const mine_idx,HeadMinerQuote const quote_type)763 void IssueHeadMinerQuote(UINT8 const mine_idx, HeadMinerQuote const quote_type)
764 {
765 Assert(mine_idx < GCM->getMines().size());
766 Assert(quote_type < NUM_HEAD_MINER_STRATEGIC_QUOTES);
767 Assert(CheckFact(FACT_MINERS_PLACED, 0));
768
769 HEAD_MINER_TYPE const& miner_data = gHeadMinerData[GetHeadMinerIndexForMine(mine_idx)];
770 auto mineData = GCM->getMine(mine_idx);
771
772 // Make sure the miner isn't dead
773 MERCPROFILESTRUCT const& p = GetProfile(miner_data.usProfileId);
774 if (p.bLife < OKLIFE)
775 {
776 SLOGD("Head Miner #%s can't talk (quote #%d)", p.zNickname.c_str(), quote_type);
777 return;
778 }
779
780 INT8 const bQuoteNum = miner_data.bQuoteNum[quote_type];
781 Assert(bQuoteNum != -1);
782
783 /* Transition to mapscreen is not necessary for "creatures gone" quote -
784 * player is IN that mine, so he'll know */
785 bool const force_mapscreen = quote_type != HEAD_MINER_STRATEGIC_QUOTE_CREATURES_GONE;
786
787 /* Decide where the miner's face and text box should be positioned in order to
788 * not obscure the mine he's in as it flashes */
789 INT16 const x = DEFAULT_EXTERN_PANEL_X_POS;
790 INT16 y = DEFAULT_EXTERN_PANEL_Y_POS;
791 if (mineData->faceDisplayYOffset)
792 {
793 y = STD_SCREEN_Y + mineData->faceDisplayYOffset;
794 }
795
796 SetExternMapscreenSpeechPanelXY(x, y);
797
798 /* Cause this quote to come up for this profile id and an indicator to flash
799 * over the mine sector */
800 HandleMinerEvent(miner_data.usProfileId, bQuoteNum, force_mapscreen);
801
802 // Stop time compression with any miner quote - these are important events.
803 StopTimeCompression();
804 }
805
806
GetHeadMinersMineIndex(UINT8 ubMinerProfileId)807 UINT8 GetHeadMinersMineIndex( UINT8 ubMinerProfileId)
808 {
809 // find which mine this guy represents
810 for (auto mine : GCM->getMines())
811 {
812 if (mine->associatedTownId == gMercProfiles[ubMinerProfileId].bTown)
813 {
814 return mine->mineId;
815 }
816 }
817
818 // not found!
819 SLOGA("Illegal profile id receieved or something is very wrong");
820 return 0;
821 }
822
823
PlayerSpokeToHeadMiner(UINT8 ubMinerProfileId)824 void PlayerSpokeToHeadMiner( UINT8 ubMinerProfileId )
825 {
826 UINT8 ubMineIndex;
827
828 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
829
830 // if this is our first time set a history fact
831 if (!gMineStatus[ubMineIndex].fSpokeToHeadMiner)
832 {
833 AddMineHistoryEvent(HISTORY_TALKED_TO_MINER, ubMineIndex);
834 gMineStatus[ ubMineIndex ].fSpokeToHeadMiner = TRUE;
835 }
836 }
837
838
IsHisMineRunningOut(UINT8 ubMinerProfileId)839 BOOLEAN IsHisMineRunningOut( UINT8 ubMinerProfileId )
840 {
841 UINT8 ubMineIndex;
842
843 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
844 return(gMineStatus[ ubMineIndex ].fRunningOut);
845 }
846
IsHisMineEmpty(UINT8 ubMinerProfileId)847 BOOLEAN IsHisMineEmpty( UINT8 ubMinerProfileId )
848 {
849 UINT8 ubMineIndex;
850
851 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
852 return(gMineStatus[ ubMineIndex ].fEmpty);
853 }
854
IsHisMineDisloyal(UINT8 ubMinerProfileId)855 BOOLEAN IsHisMineDisloyal( UINT8 ubMinerProfileId )
856 {
857 UINT8 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
858 UINT8 ubAssociatedTown = GCM->getMine(ubMineIndex)->associatedTownId;
859
860 if (gTownLoyalty[ubAssociatedTown].ubRating < LOW_MINE_LOYALTY_THRESHOLD)
861 {
862 // pretty disloyal
863 return(TRUE);
864 }
865 else
866 {
867 // pretty loyal
868 return(FALSE);
869 }
870 }
871
IsHisMineInfested(UINT8 ubMinerProfileId)872 BOOLEAN IsHisMineInfested( UINT8 ubMinerProfileId )
873 {
874 UINT8 ubMineIndex;
875
876 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
877 return(!MineClearOfMonsters( ubMineIndex ));
878 }
879
IsHisMineLostAndRegained(UINT8 ubMinerProfileId)880 BOOLEAN IsHisMineLostAndRegained( UINT8 ubMinerProfileId )
881 {
882 UINT8 ubMineIndex;
883
884 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
885
886 if (PlayerControlsMine(ubMineIndex) && gMineStatus[ ubMineIndex ].fQueenRetookProducingMine)
887 {
888 return(TRUE);
889 }
890 else
891 {
892 return(FALSE);
893 }
894 }
895
ResetQueenRetookMine(UINT8 ubMinerProfileId)896 void ResetQueenRetookMine( UINT8 ubMinerProfileId )
897 {
898 UINT8 ubMineIndex;
899
900 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
901
902 gMineStatus[ ubMineIndex ].fQueenRetookProducingMine = FALSE;
903 }
904
IsHisMineAtMaxProduction(UINT8 ubMinerProfileId)905 BOOLEAN IsHisMineAtMaxProduction( UINT8 ubMinerProfileId )
906 {
907 UINT8 ubMineIndex;
908
909 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
910
911 if (GetAvailableWorkForceForMineForPlayer( ubMineIndex ) == 100)
912 {
913 // loyalty is 100% and control is 100%
914 return(TRUE);
915 }
916 else
917 {
918 // something not quite perfect yet
919 return(FALSE);
920 }
921 }
922
923
QueenHasRegainedMineSector(UINT8 ubMineIndex)924 void QueenHasRegainedMineSector(UINT8 ubMineIndex)
925 {
926 Assert(ubMineIndex < gMineStatus.size());
927
928 if (gMineStatus[ ubMineIndex ].fMineHasProducedForPlayer)
929 {
930 gMineStatus[ ubMineIndex ].fQueenRetookProducingMine = TRUE;
931 }
932 }
933
934
HasAnyMineBeenAttackedByMonsters(void)935 BOOLEAN HasAnyMineBeenAttackedByMonsters(void)
936 {
937 UINT8 ubMineIndex;
938
939 // find which mine this guy represents
940 for( ubMineIndex = 0; ubMineIndex < gMineStatus.size(); ubMineIndex++ )
941 {
942 if (!MineClearOfMonsters( ubMineIndex ) || gMineStatus[ ubMineIndex ].fPrevInvadedByMonsters)
943 {
944 return(TRUE);
945 }
946 }
947
948 return(FALSE);
949 }
950
951
PlayerAttackedHeadMiner(UINT8 ubMinerProfileId)952 void PlayerAttackedHeadMiner( UINT8 ubMinerProfileId )
953 {
954 UINT8 ubMineIndex;
955 INT8 bTownId;
956
957 // get the index of his mine
958 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
959
960 // if it's the first time he's been attacked
961 if (!gMineStatus[ubMineIndex].fAttackedHeadMiner)
962 {
963 // shut off production at his mine (Permanently!)
964 ShutOffMineProduction( ubMineIndex );
965 MineShutdownIsPermanent( ubMineIndex );
966
967 // get the index of his town
968 bTownId = GetTownAssociatedWithMine( ubMineIndex );
969 // penalize associated town's loyalty
970 DecrementTownLoyalty( bTownId, LOYALTY_PENALTY_HEAD_MINER_ATTACKED );
971
972 // don't allow this more than once
973 gMineStatus[ ubMineIndex ].fAttackedHeadMiner = TRUE;
974 }
975 }
976
977
HasHisMineBeenProducingForPlayerForSomeTime(UINT8 ubMinerProfileId)978 BOOLEAN HasHisMineBeenProducingForPlayerForSomeTime( UINT8 ubMinerProfileId )
979 {
980 UINT8 ubMineIndex;
981
982 ubMineIndex = GetHeadMinersMineIndex( ubMinerProfileId );
983
984 if ( gMineStatus[ ubMineIndex ].fMineHasProducedForPlayer &&
985 ( ( GetWorldTotalMin() - gMineStatus[ ubMineIndex ].uiTimePlayerProductionStarted ) >= ( 24 * 60 ) ) )
986 {
987 return ( TRUE );
988 }
989
990 return( FALSE );
991 }
992
993
GetIdOfMineForSector(INT16 const x,INT16 const y,INT8 const z)994 INT8 GetIdOfMineForSector(INT16 const x, INT16 const y, INT8 const z)
995 {
996 auto mine = GCM->getMineForSector(x, y, z);
997 if (mine != NULL)
998 {
999 return mine->mineId;
1000 }
1001
1002 return -1;
1003 }
1004
1005
1006 // use this to determine whether or not to place miners into a underground mine level
AreThereMinersInsideThisMine(UINT8 ubMineIndex)1007 BOOLEAN AreThereMinersInsideThisMine( UINT8 ubMineIndex )
1008 {
1009 MINE_STATUS_TYPE *pMineStatus;
1010
1011
1012 Assert(ubMineIndex < gMineStatus.size());
1013
1014 pMineStatus = &(gMineStatus[ ubMineIndex ]);
1015
1016 // mine not empty
1017 // mine clear of any monsters
1018 // the "shutdown permanently" flag is only used for the player never receiving the income - miners will keep mining
1019 if ( ( !pMineStatus->fEmpty ) && MineClearOfMonsters( ubMineIndex ) )
1020 {
1021 return( TRUE );
1022 }
1023
1024 return( FALSE );
1025 }
1026
1027 // returns whether or not we've spoken to the head miner of a particular mine
SpokenToHeadMiner(UINT8 ubMineIndex)1028 BOOLEAN SpokenToHeadMiner( UINT8 ubMineIndex )
1029 {
1030 return( gMineStatus[ ubMineIndex ].fSpokeToHeadMiner );
1031 }
1032