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