1 #include "Campaign_Init.h"
2 #include "Overhead.h"
3 #include "FileMan.h"
4 #include "Creature_Spreading.h"
5 #include "Campaign_Types.h"
6 #include "Queen_Command.h"
7 #include "Strategic_Movement.h"
8 #include "Game_Event_Hook.h"
9 #include "GameSettings.h"
10 #include "Random.h"
11 #include "Message.h"
12 #include "Font_Control.h"
13 #include "Soldier_Init_List.h"
14 #include "Lighting.h"
15 #include "StrategicMap.h"
16 #include "Game_Clock.h"
17 #include "Strategic_Mines.h"
18 #include "Music_Control.h"
19 #include "ContentMusic.h"
20 #include "Strategic.h"
21 #include "JAScreens.h"
22 #include "Town_Militia.h"
23 #include "Strategic_Town_Loyalty.h"
24 #include "PreBattle_Interface.h"
25 #include "Map_Edgepoints.h"
26 #include "Animation_Data.h"
27 #include "OppList.h"
28 #include "Meanwhile.h"
29 #include "Strategic_AI.h"
30 #include "Map_Information.h"
31 #include "MemMan.h"
32 #include "Debug.h"
33 #include "ScreenIDs.h"
34 #include "GameInstance.h"
35 #include "ContentManager.h"
36 #include "MineModel.h"
37 #include "CreatureLairModel.h"
38 #include "GameInstance.h"
39 #include "ContentManager.h"
40 #include "MineModel.h"
41
42 #include <algorithm>
43 #include <stdexcept>
44 #include <vector>
45
46 //GAME BALANCING DEFINITIONS FOR CREATURE SPREADING
47 //Hopefully, adjusting these following definitions will ease the balancing of the
48 //creature spreading.
49 //The one note here is that for any definitions that have a XXX_BONUS at the end of a definition,
50 //it gets added on to it's counterpart via:
51 // XXX_VALUE + Random( 1 + XXX_BONUS )
52
53 //This is how often the creatures spread, once the quest begins. The smaller the gap,
54 //the faster the creatures will advance. This is also directly related to the reproduction
55 //rates which are applied each time the creatures spread.
56 #define EASY_SPREAD_TIME_IN_MINUTES 510 //easy spreads every 8.5 hours
57 #define NORMAL_SPREAD_TIME_IN_MINUTES 450 //normal spreads every 7.5 hours
58 #define HARD_SPREAD_TIME_IN_MINUTES 390 //hard spreads every 6.5 hours
59
60 //Once the queen is added to the game, we can instantly let her spread x number of times
61 //to give her a head start. This can also be a useful tool for having slow reproduction rates
62 //but quicker head start to compensate to make the creatures less aggressive overall.
63 #define EASY_QUEEN_INIT_BONUS_SPREADS 1
64 #define NORMAL_QUEEN_INIT_BONUS_SPREADS 2
65 #define HARD_QUEEN_INIT_BONUS_SPREADS 3
66
67 //This value modifies the chance to populate a given sector. This is different from the previous definition.
68 //This value gets applied to a potentially complicated formula, using the creature habitat to modify
69 //chance to populate, along with factoring in the relative distance to the hive range (to promote deeper lair
70 //population increases), etc. I would recommend not tweaking the value too much in either direction from
71 //zero due to the fact that this can greatly effect spread times and maximum populations. Basically, if the
72 //creatures are spreading too quickly, increase the value, otherwise decrease it to a negative value
73 #define EASY_POPULATION_MODIFIER 0
74 #define NORMAL_POPULATION_MODIFIER 0
75 #define HARD_POPULATION_MODIFIER 0
76
77 //Augments the chance that the creatures will attack a town. The conditions for attacking a town
78 //are based strictly on the occupation of the creatures in each of the four mine exits. For each creature
79 //there is a base chance of 10% that the creatures will feed sometime during the night.
80 #define EASY_CREATURE_TOWN_AGGRESSIVENESS -10
81 #define NORMAL_CREATURE_TOWN_AGGRESSIVENESS 0
82 #define HARD_CREATURE_TOWN_AGGRESSIVENESS 10
83
84
85 //This is how many creatures the queen produces for each cycle of spreading. The higher
86 //the numbers the faster the creatures will advance.
87 #define EASY_QUEEN_REPRODUCTION_BASE 6 //6-7
88 #define EASY_QUEEN_REPRODUCTION_BONUS 1
89 #define NORMAL_QUEEN_REPRODUCTION_BASE 7 //7-9
90 #define NORMAL_QUEEN_REPRODUCTION_BONUS 2
91 #define HARD_QUEEN_REPRODUCTION_BASE 9 //9-12
92 #define HARD_QUEEN_REPRODUCTION_BONUS 3
93
94 //When either in a cave level with blue lights or there is a creature presence, then
95 //we override the normal music with the creature music. The conditions are maintained
96 //inside the function PrepareCreaturesForBattle() in this module.
97 BOOLEAN gfUseCreatureMusic = FALSE;
98 BOOLEAN gfCreatureMeanwhileScenePlayed = FALSE;
99
100 struct CREATURE_DIRECTIVE
101 {
102 CREATURE_DIRECTIVE* next;
103 UNDERGROUND_SECTORINFO *pLevel;
104 };
105
106 CREATURE_DIRECTIVE *gLair;
107 const CreatureLairModel* gLairModel;
108
109 INT32 giHabitatedDistance = 0;
110 INT32 giPopulationModifier = 0;
111 INT32 giLairID = 0;
112 INT32 giDestroyedLairID = 0;
113
114 //various information required for keeping track of the battle sector involved for
115 //prebattle interface, autoresolve, etc.
116 INT16 gsCreatureInsertionCode = 0;
117 INT16 gsCreatureInsertionGridNo = 0;
118 UINT8 gubNumCreaturesAttackingTown = 0;
119 UINT8 gubYoungMalesAttackingTown = 0;
120 UINT8 gubYoungFemalesAttackingTown = 0;
121 UINT8 gubAdultMalesAttackingTown = 0;
122 UINT8 gubAdultFemalesAttackingTown = 0;
123 UINT8 gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE;
124 UINT8 gubSectorIDOfCreatureAttack = 0;
125
126
NewDirective(UINT8 ubSectorID,UINT8 ubSectorZ,UINT8 ubCreatureHabitat)127 static CREATURE_DIRECTIVE* NewDirective(UINT8 ubSectorID, UINT8 ubSectorZ, UINT8 ubCreatureHabitat)
128 {
129 UINT8 ubSectorX, ubSectorY;
130 CREATURE_DIRECTIVE* const curr = new CREATURE_DIRECTIVE{};
131 ubSectorX = (UINT8)((ubSectorID % 16) + 1);
132 ubSectorY = (UINT8)((ubSectorID / 16) + 1);
133 curr->pLevel = FindUnderGroundSector( ubSectorX, ubSectorY, ubSectorZ );
134 if( !curr->pLevel )
135 {
136 SLOGA("Could not find underground sector node (%c%db_%d) that should exist.",
137 ubSectorY + 'A' - 1, ubSectorX, ubSectorZ);
138 delete curr;
139 return 0;
140 }
141
142 curr->pLevel->ubCreatureHabitat = ubCreatureHabitat;
143 curr->next = NULL;
144 return curr;
145 }
146
InitCreatureLair(const CreatureLairModel * lairModel)147 static void InitCreatureLair(const CreatureLairModel* lairModel)
148 {
149 gLairModel = lairModel;
150
151 CREATURE_DIRECTIVE* curr = NULL;
152 giLairID = lairModel->lairId;
153
154 //initialize the linked list of lairs
155 for (auto sec : lairModel->lairSectors)
156 {
157 auto next = NewDirective(sec.sectorId, sec.sectorLevel, sec.habitatType);
158 if (sec.habitatType == QUEEN_LAIR && !next->pLevel->ubNumCreatures)
159 {
160 next->pLevel->ubNumCreatures = 1; //for the queen.
161 }
162
163 if (curr == NULL)
164 { // first node, set gLair to the start of list
165 gLair = next;
166 }
167 else
168 { // append to list
169 curr->next = next;
170 }
171 curr = next;
172 }
173 }
174
IsMineInfestible(const MineModel * mine)175 static bool IsMineInfestible(const MineModel* mine)
176 { // If neither head miner was attacked, ore will/has run out nor enemy controlled
177 UINT8 id = mine->mineId;
178 MINE_STATUS_TYPE const& m = gMineStatus[id];
179 return !m.fAttackedHeadMiner &&
180 m.uiOreRunningOutPoint == 0 &&
181 !StrategicMap[SECTOR_INFO_TO_STRATEGIC_INDEX(mine->entranceSector)].fEnemyControlled;
182 }
183
184
InitCreatureQuest()185 void InitCreatureQuest()
186 {
187 INT32 i=-1;
188 UINT8 ubChosenMineId;
189 INT32 iRandom;
190 INT32 iNumMinesInfestible;
191 std::vector<UINT8> ubMinesInfestible;
192
193 if( giLairID )
194 {
195 return; //already active!
196 }
197
198 if( !gfCreatureMeanwhileScenePlayed )
199 {
200 //Start the meanwhile scene for the queen ordering the release of the creatures.
201 HandleCreatureRelease();
202 gfCreatureMeanwhileScenePlayed = TRUE;
203 }
204
205 giHabitatedDistance = 0;
206 switch( gGameOptions.ubDifficultyLevel )
207 {
208 case DIF_LEVEL_EASY:
209 giPopulationModifier = EASY_POPULATION_MODIFIER;
210 break;
211 case DIF_LEVEL_MEDIUM:
212 giPopulationModifier = NORMAL_POPULATION_MODIFIER;
213 break;
214 case DIF_LEVEL_HARD:
215 giPopulationModifier = HARD_POPULATION_MODIFIER;
216 break;
217 }
218
219 /* Determine which of the four mines are infectible by creatures. Infectible
220 * mines are those that are player controlled and unlimited. We don't want the
221 * creatures to infect the mine that runs out. */
222 for (auto lair : GCM->getCreatureLairs())
223 {
224 auto mine = GCM->getMine(lair->associatedMineId);
225 if (IsMineInfestible(mine))
226 {
227 ubMinesInfestible.push_back(mine->mineId);
228 }
229 }
230
231 iNumMinesInfestible = ubMinesInfestible.size();
232 if( !iNumMinesInfestible )
233 {
234 return;
235 }
236
237 //Choose one of the infectible mines randomly
238 iRandom = Random( iNumMinesInfestible );
239 ubChosenMineId = ubMinesInfestible[iRandom];
240
241 //Now, choose a start location for the queen.
242 auto lairModel = GCM->getCreatureLairByMineId(ubChosenMineId);
243 InitCreatureLair(lairModel);
244
245 // enable the lair entrance
246 UINT8 entranceSector = lairModel->entranceSector;
247 UNDERGROUND_SECTORINFO* lairEntrance = FindUnderGroundSector(SECTORX(entranceSector), SECTORY(entranceSector), lairModel->entranceSectorLevel);
248 if (lairEntrance == NULL)
249 {
250 throw std::runtime_error("Lair entrance sector is not defined as an underground sector");
251 }
252 lairEntrance->uiFlags |= SF_PENDING_ALTERNATE_MAP;
253
254 //Now determine how often we will spread the creatures.
255 switch( gGameOptions.ubDifficultyLevel )
256 {
257 case DIF_LEVEL_EASY:
258 i = EASY_QUEEN_INIT_BONUS_SPREADS;
259 AddPeriodStrategicEvent( EVENT_CREATURE_SPREAD, EASY_SPREAD_TIME_IN_MINUTES, 0 );
260 break;
261 case DIF_LEVEL_MEDIUM:
262 i = NORMAL_QUEEN_INIT_BONUS_SPREADS;
263 AddPeriodStrategicEvent( EVENT_CREATURE_SPREAD, NORMAL_SPREAD_TIME_IN_MINUTES, 0 );
264 break;
265 case DIF_LEVEL_HARD:
266 i = HARD_QUEEN_INIT_BONUS_SPREADS;
267 AddPeriodStrategicEvent( EVENT_CREATURE_SPREAD, HARD_SPREAD_TIME_IN_MINUTES, 0 );
268 break;
269 }
270
271 //Set things up so that the creatures can plan attacks on helpless miners and civilians while
272 //they are sleeping. They do their planning at 10PM every day, and decide to attack sometime
273 //during the night.
274 AddEveryDayStrategicEvent( EVENT_CREATURE_NIGHT_PLANNING, 1320, 0 );
275
276 //Got to give the queen some early protection, so do some creature spreading.
277 while( i-- )
278 { //# times spread is based on difficulty, and the values in the defines.
279 SpreadCreatures();
280 }
281 }
282
283
AddCreatureToNode(CREATURE_DIRECTIVE * node)284 static void AddCreatureToNode(CREATURE_DIRECTIVE* node)
285 {
286 node->pLevel->ubNumCreatures++;
287
288 if( node->pLevel->uiFlags & SF_PENDING_ALTERNATE_MAP )
289 { //there is an alternate map meaning that there is a dynamic opening. From now on
290 //we substitute this map.
291 node->pLevel->uiFlags &= ~SF_PENDING_ALTERNATE_MAP;
292 node->pLevel->uiFlags |= SF_USE_ALTERNATE_MAP;
293 }
294 }
295
296
PlaceNewCreature(CREATURE_DIRECTIVE * node,INT32 iDistance)297 static BOOLEAN PlaceNewCreature(CREATURE_DIRECTIVE* node, INT32 iDistance)
298 {
299 if( !node )
300 return FALSE;
301 //check to see if the creatures are permitted to spread into certain areas. There are 4 mines (human perspective), and
302 //creatures won't spread to them until the player controls them. Additionally, if the player has recently cleared the
303 //mine, then temporarily prevent the spreading of creatures.
304
305 if( giHabitatedDistance == iDistance )
306 { //FRONT-LINE CONDITIONS -- consider expansion or frontline fortification. The formulae used
307 //in this sector are geared towards outer expansion.
308 //we have reached the distance limitation for the spreading. We will determine if
309 //the area is populated enough to spread further. The minimum population must be 4 before
310 //spreading is even considered.
311 if( node->pLevel->ubNumCreatures*10 - 10 <= (INT32)Random( 60 ) )
312 {
313 // x<=1 100%
314 // x==2 83%
315 // x==3 67%
316 // x==4 50%
317 // x==5 33%
318 // x==6 17%
319 // x>=7 0%
320 AddCreatureToNode( node );
321 return TRUE;
322 }
323 }
324 else if( giHabitatedDistance > iDistance )
325 { //we are within the "safe" habitated area of the creature's area of influence. The chance of
326 //increasing the population inside this sector depends on how deep we are within the sector.
327 if( node->pLevel->ubNumCreatures < MAX_STRATEGIC_TEAM_SIZE ||
328 (node->pLevel->ubNumCreatures < 32 && node->pLevel->ubCreatureHabitat == QUEEN_LAIR) )
329 { //there is ALWAYS a chance to habitate an interior sector, though the chances are slim for
330 //highly occupied sectors. This chance is modified by the type of area we are in.
331 INT32 iAbsoluteMaxPopulation;
332 INT32 iMaxPopulation=-1;
333 INT32 iChanceToPopulate;
334 switch( node->pLevel->ubCreatureHabitat )
335 {
336 case QUEEN_LAIR: //Defend the queen bonus
337 iAbsoluteMaxPopulation = 32;
338 break;
339 case LAIR: //Smaller defend the queen bonus
340 iAbsoluteMaxPopulation = 18;
341 break;
342 case LAIR_ENTRANCE: //Smallest defend the queen bonus
343 iAbsoluteMaxPopulation = 15;
344 break;
345 case INNER_MINE: //neg bonus -- actually promotes expansion over population, and decrease max pop here.
346 iAbsoluteMaxPopulation = 12;
347 break;
348 case OUTER_MINE: //neg bonus -- actually promotes expansion over population, and decrease max pop here.
349 iAbsoluteMaxPopulation = 10;
350 break;
351 case FEEDING_GROUNDS: //get free food bonus! yummy humans :)
352 iAbsoluteMaxPopulation = 15;
353 break;
354 case MINE_EXIT: //close access to humans (don't want to overwhelm them)
355 iAbsoluteMaxPopulation = 10;
356 break;
357 default:
358 SLOGA("PlaceNewCreature: invalid habitat type");
359 return FALSE;
360 }
361
362 switch( gGameOptions.ubDifficultyLevel )
363 {
364 case DIF_LEVEL_EASY: //50%
365 iAbsoluteMaxPopulation /= 2; //Half
366 break;
367 case DIF_LEVEL_MEDIUM: //80%
368 iAbsoluteMaxPopulation = iAbsoluteMaxPopulation * 4 / 5;
369 break;
370 case DIF_LEVEL_HARD: //100%
371 break;
372 }
373
374 //Calculate the desired max population percentage based purely on current distant to creature range.
375 //The closer we are to the lair, the closer this value will be to 100.
376 iMaxPopulation = 100 - iDistance * 100 / giHabitatedDistance;
377 iMaxPopulation = MAX( iMaxPopulation, 25 );
378 //Now, convert the previous value into a numeric population.
379 iMaxPopulation = iAbsoluteMaxPopulation * iMaxPopulation / 100;
380 iMaxPopulation = MAX( iMaxPopulation, 4 );
381
382
383 //The chance to populate a sector is higher for lower populations. This is calculated on
384 //the ratio of current population to the max population.
385 iChanceToPopulate = 100 - node->pLevel->ubNumCreatures * 100 / iMaxPopulation;
386
387 if( !node->pLevel->ubNumCreatures || (iChanceToPopulate > (INT32)Random( 100 )
388 && iMaxPopulation > node->pLevel->ubNumCreatures) )
389 {
390 AddCreatureToNode( node );
391 return TRUE;
392 }
393 }
394 }
395 else
396 { //we are in a new area, so we will populate it
397 AddCreatureToNode( node );
398 giHabitatedDistance++;
399 return TRUE;
400 }
401 if( PlaceNewCreature( node->next, iDistance + 1 ) )
402 return TRUE;
403 return FALSE;
404 }
405
SpreadCreatures()406 void SpreadCreatures()
407 {
408 UINT16 usNewCreatures=0;
409
410 if (giLairID == -1) return;
411
412 //queen just produced a litter of creature larvae. Let's do some spreading now.
413 switch( gGameOptions.ubDifficultyLevel )
414 {
415 case DIF_LEVEL_EASY:
416 usNewCreatures = (UINT16)(EASY_QUEEN_REPRODUCTION_BASE + Random( 1 + EASY_QUEEN_REPRODUCTION_BONUS ));
417 break;
418 case DIF_LEVEL_MEDIUM:
419 usNewCreatures = (UINT16)(NORMAL_QUEEN_REPRODUCTION_BASE + Random( 1 + NORMAL_QUEEN_REPRODUCTION_BONUS ));
420 break;
421 case DIF_LEVEL_HARD:
422 usNewCreatures = (UINT16)(HARD_QUEEN_REPRODUCTION_BASE + Random( 1 + HARD_QUEEN_REPRODUCTION_BONUS ));
423 break;
424 }
425
426 while( usNewCreatures-- )
427 {
428 //Note, this function can and will fail if the population gets dense. This is a necessary
429 //feature. Otherwise, the queen would fill all the cave levels with MAX_STRATEGIC_TEAM_SIZE monsters, and that would
430 //be bad.
431 PlaceNewCreature( gLair, 0 );
432 }
433 }
434
435
AddCreaturesToBattle(UINT8 n_young_males,UINT8 n_young_females,UINT8 n_adult_males,UINT8 n_adult_females)436 static void AddCreaturesToBattle(UINT8 n_young_males, UINT8 n_young_females, UINT8 n_adult_males, UINT8 n_adult_females)
437 {
438 INT16 const insertion_code = gsCreatureInsertionCode;
439 UINT8 desired_direction;
440 switch (insertion_code)
441 {
442 case INSERTION_CODE_NORTH: desired_direction = SOUTHEAST; break;
443 case INSERTION_CODE_EAST: desired_direction = SOUTHWEST; break;
444 case INSERTION_CODE_SOUTH: desired_direction = NORTHWEST; break;
445 case INSERTION_CODE_WEST: desired_direction = NORTHEAST; break;
446 case INSERTION_CODE_GRIDNO: desired_direction = 0; break;
447 default: throw std::logic_error("Invalid direction passed to AddCreaturesToBattle()");
448 }
449
450 MAPEDGEPOINTINFO edgepoint_info;
451 if (insertion_code != INSERTION_CODE_GRIDNO)
452 {
453 ChooseMapEdgepoints(&edgepoint_info, insertion_code, n_young_males + n_young_females + n_adult_males + n_adult_females);
454 }
455
456 std::vector<SoldierBodyType> bodies;
457 bodies.insert(bodies.end(), n_young_males, YAM_MONSTER);
458 bodies.insert(bodies.end(), n_young_females, YAF_MONSTER);
459 bodies.insert(bodies.end(), n_adult_males, AM_MONSTER);
460 bodies.insert(bodies.end(), n_adult_females, ADULTFEMALEMONSTER);
461 std::shuffle(bodies.begin(), bodies.end(), gRandomEngine);
462
463 UINT8 slot = 0;
464 for (SoldierBodyType const body : bodies)
465 {
466 SOLDIERTYPE* const s = TacticalCreateCreature(body);
467 s->bHunting = TRUE;
468 s->ubInsertionDirection = desired_direction;
469 s->ubStrategicInsertionCode = INSERTION_CODE_GRIDNO;
470 if (insertion_code == INSERTION_CODE_GRIDNO)
471 {
472 s->usStrategicInsertionData = gsCreatureInsertionGridNo;
473 }
474 else if (slot < edgepoint_info.ubNumPoints)
475 { // Use an edgepoint
476 s->usStrategicInsertionData = edgepoint_info.sGridNo[slot++];
477 }
478 else
479 { // No edgepoints left, so put him at the entrypoint
480 s->ubStrategicInsertionCode = insertion_code;
481 }
482 UpdateMercInSector(*s, gWorldSectorX, gWorldSectorY, 0);
483 }
484
485 gsCreatureInsertionCode = 0;
486 gsCreatureInsertionGridNo = 0;
487 gubNumCreaturesAttackingTown = 0;
488 gubYoungMalesAttackingTown = 0;
489 gubYoungFemalesAttackingTown = 0;
490 gubAdultMalesAttackingTown = 0;
491 gubAdultFemalesAttackingTown = 0;
492 gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE;
493 gubSectorIDOfCreatureAttack = 0;
494 AllTeamsLookForAll(FALSE);
495 }
496
497
ChooseTownSectorToAttack(UINT8 ubSectorID,BOOLEAN fSpecificSector)498 static void ChooseTownSectorToAttack(UINT8 ubSectorID, BOOLEAN fSpecificSector)
499 {
500 const CreatureAttackSector* attackDetails = NULL;
501 if (gLairModel == NULL)
502 {
503 SLOGA("gLairModel is NULL. Something wrong!");
504 return;
505 }
506
507 // determine town sector to attack
508 attackDetails = (fSpecificSector) ?
509 gLairModel->getTownAttackDetails(ubSectorID) : // attack the given sector
510 gLairModel->chooseTownSectorToAttack() // pick a sector to attack
511 ;
512 if (!attackDetails)
513 {
514 SLOGA("ChooseTownSectorToAttack: invalid SectorID");
515 return;
516 }
517
518 // determine how the enemies enter the sector
519 gubSectorIDOfCreatureAttack = attackDetails->sectorId;
520 gsCreatureInsertionCode = attackDetails->insertionCode;
521 if (gsCreatureInsertionCode == INSERTION_CODE_GRIDNO)
522 {
523 gsCreatureInsertionGridNo = attackDetails->insertionGridNo;
524 }
525 }
526
CreatureAttackTown(UINT8 ubSectorID,BOOLEAN fSpecificSector)527 void CreatureAttackTown(UINT8 ubSectorID, BOOLEAN fSpecificSector)
528 { //This is the launching point of the creature attack.
529 UNDERGROUND_SECTORINFO *pSector;
530 UINT8 ubSectorX, ubSectorY;
531
532 if( gfWorldLoaded && gTacticalStatus.fEnemyInSector )
533 { //Battle currently in progress, repost the event
534 AddStrategicEvent( EVENT_CREATURE_ATTACK, GetWorldTotalMin() + Random( 10 ), ubSectorID );
535 return;
536 }
537
538 gubCreatureBattleCode = CREATURE_BATTLE_CODE_NONE;
539
540 ubSectorX = SECTORX(ubSectorID);
541 ubSectorY = SECTORY(ubSectorID);
542
543 if (!fSpecificSector)
544 {
545 //Record the number of creatures in the sector.
546 pSector = FindUnderGroundSector( ubSectorX, ubSectorY, 1 );
547 if( !pSector )
548 {
549 CreatureAttackTown(ubSectorID, TRUE);
550 return;
551 }
552 gubNumCreaturesAttackingTown = pSector->ubNumCreatures;
553 if( !gubNumCreaturesAttackingTown )
554 {
555 CreatureAttackTown(ubSectorID, TRUE);
556 return;
557 }
558
559 pSector->ubNumCreatures = 0;
560
561 //Choose one of the town sectors to attack. Sectors closer to
562 //the mine entrance have a greater chance of being chosen.
563 ChooseTownSectorToAttack( ubSectorID, FALSE );
564 ubSectorX = SECTORX(gubSectorIDOfCreatureAttack);
565 ubSectorY = SECTORY(gubSectorIDOfCreatureAttack);
566 }
567 else
568 {
569 ChooseTownSectorToAttack(ubSectorID, TRUE);
570 gubNumCreaturesAttackingTown = 5;
571 }
572
573 //Now that the sector has been chosen, attack it!
574 if( PlayerGroupsInSector( ubSectorX, ubSectorY, 0 ) )
575 { //we have players in the sector
576 if( ubSectorX == gWorldSectorX && ubSectorY == gWorldSectorY && !gbWorldSectorZ )
577 { //This is the currently loaded sector. All we have to do is change the music and insert
578 //the creatures tactically.
579 if( guiCurrentScreen == GAME_SCREEN )
580 {
581 gubCreatureBattleCode = CREATURE_BATTLE_CODE_TACTICALLYADD;
582 }
583 else
584 {
585 gubCreatureBattleCode = CREATURE_BATTLE_CODE_PREBATTLEINTERFACE;
586 }
587 }
588 else
589 {
590 gubCreatureBattleCode = CREATURE_BATTLE_CODE_PREBATTLEINTERFACE;
591 }
592 }
593 else if( CountAllMilitiaInSector( ubSectorX, ubSectorY ) )
594 { //we have militia in the sector
595 gubCreatureBattleCode = CREATURE_BATTLE_CODE_AUTORESOLVE;
596 }
597 else if( !StrategicMap[ ubSectorX + MAP_WORLD_X * ubSectorY ].fEnemyControlled )
598 { //player controlled sector -- eat some civilians
599 AdjustLoyaltyForCivsEatenByMonsters( ubSectorX, ubSectorY, gubNumCreaturesAttackingTown );
600 SectorInfo[ ubSectorID ].ubDayOfLastCreatureAttack = (UINT8)GetWorldDay();
601 return;
602 }
603 else
604 { //enemy controlled sectors don't get attacked.
605 return;
606 }
607
608 SectorInfo[ ubSectorID ].ubDayOfLastCreatureAttack = (UINT8)GetWorldDay();
609 switch( gubCreatureBattleCode )
610 {
611 case CREATURE_BATTLE_CODE_AUTORESOLVE:
612 gfAutomaticallyStartAutoResolve = TRUE;
613 /* FALLTHROUGH */
614 case CREATURE_BATTLE_CODE_PREBATTLEINTERFACE:
615 InitPreBattleInterface(0, true);
616 break;
617 case CREATURE_BATTLE_CODE_TACTICALLYADD:
618 PrepareCreaturesForBattle();
619 break;
620 }
621 InterruptTime();
622 PauseGame();
623 LockPauseState(LOCK_PAUSE_CREATURE_ATTACK);
624 }
625
626
DeleteDirectiveNode(CREATURE_DIRECTIVE ** node)627 static void DeleteDirectiveNode(CREATURE_DIRECTIVE** node)
628 {
629 if( (*node)->next )
630 DeleteDirectiveNode( &((*node)->next) );
631 delete *node;
632 *node = NULL;
633 }
634
635 //Recursively delete all nodes (from the top down).
DeleteCreatureDirectives()636 void DeleteCreatureDirectives()
637 {
638 if( gLair )
639 DeleteDirectiveNode( &gLair );
640 giLairID = 0;
641 }
642
EndCreatureQuest()643 void EndCreatureQuest()
644 {
645 CREATURE_DIRECTIVE *curr;
646 UNDERGROUND_SECTORINFO *pSector;
647 INT32 i;
648
649 //By setting the lairID to -1, when it comes time to spread creatures,
650 //They will get subtracted instead.
651 giDestroyedLairID = giLairID;
652 giLairID = -1;
653
654 //Also nuke all of the creatures in all of the other mine sectors. This
655 //is keyed on the fact that the queen monster is killed.
656 curr = gLair;
657 if( curr )
658 { //skip first node (there could be other creatures around.
659 curr = curr->next;
660 }
661 while( curr )
662 {
663 curr->pLevel->ubNumCreatures = 0;
664 curr = curr->next;
665 }
666
667 //Remove the creatures that are trapped underneath Tixa
668 pSector = FindUnderGroundSector( 9, 10, 2 );
669 if( pSector )
670 {
671 pSector->ubNumCreatures = 0;
672 }
673
674 //Also find and nuke all creatures on any surface levels!!!
675 //KM: Sept 3, 1999 patch
676 for( i = 0; i < 255; i++ )
677 {
678 SectorInfo[ i ].ubNumCreatures = 0;
679 SectorInfo[ i ].ubCreaturesInBattle = 0;
680 }
681 }
682
683
CreaturesInUndergroundSector(UINT8 ubSectorID,UINT8 ubSectorZ)684 static UINT8 CreaturesInUndergroundSector(UINT8 ubSectorID, UINT8 ubSectorZ)
685 {
686 UNDERGROUND_SECTORINFO *pSector;
687 UINT8 ubSectorX, ubSectorY;
688 ubSectorX = (UINT8)SECTORX( ubSectorID );
689 ubSectorY = (UINT8)SECTORY( ubSectorID );
690 pSector = FindUnderGroundSector( ubSectorX, ubSectorY, ubSectorZ );
691 if( pSector )
692 return pSector->ubNumCreatures;
693 return 0;
694 }
695
MineClearOfMonsters(UINT8 ubMineIndex)696 BOOLEAN MineClearOfMonsters( UINT8 ubMineIndex )
697 {
698 Assert(ubMineIndex < MAX_NUMBER_OF_MINES);
699
700 if( !gMineStatus[ ubMineIndex ].fPrevInvadedByMonsters )
701 {
702 auto mine = GCM->getMine(ubMineIndex);
703 if (mine == NULL)
704 {
705 SLOGE("Attempting to check if mine is clear but mine index is invalid (%d).", ubMineIndex);
706 return true;
707 }
708 for (auto sector : mine->mineSectors)
709 {
710 if (CreaturesInUndergroundSector(sector[0], sector[1]))
711 {
712 return false;
713 }
714 }
715 }
716 else
717 { //mine was previously invaded by creatures. Don't allow mine production until queen is dead.
718 if( giLairID != -1 )
719 {
720 return FALSE;
721 }
722 }
723 return TRUE;
724 }
725
DetermineCreatureTownComposition(UINT8 ubNumCreatures,UINT8 * pubNumYoungMales,UINT8 * pubNumYoungFemales,UINT8 * pubNumAdultMales,UINT8 * pubNumAdultFemales)726 void DetermineCreatureTownComposition(UINT8 ubNumCreatures,
727 UINT8 *pubNumYoungMales, UINT8 *pubNumYoungFemales,
728 UINT8 *pubNumAdultMales, UINT8 *pubNumAdultFemales)
729 {
730 INT32 i, iRandom;
731 UINT8 ubYoungMalePercentage = 10;
732 UINT8 ubYoungFemalePercentage = 65;
733 UINT8 ubAdultMalePercentage = 5;
734 UINT8 ubAdultFemalePercentage = 20;
735
736 //First step is to convert the percentages into the numbers we will use.
737 ubYoungFemalePercentage += ubYoungMalePercentage;
738 ubAdultMalePercentage += ubYoungFemalePercentage;
739 ubAdultFemalePercentage += ubAdultMalePercentage;
740 if( ubAdultFemalePercentage != 100 )
741 {
742 SLOGA("Percentage for adding creatures don't add up to 100." );
743 }
744 //Second step is to determine the breakdown of the creatures randomly.
745 i = ubNumCreatures;
746 while( i-- )
747 {
748 iRandom = Random( 100 );
749 if( iRandom < ubYoungMalePercentage )
750 (*pubNumYoungMales)++;
751 else if( iRandom < ubYoungFemalePercentage )
752 (*pubNumYoungFemales)++;
753 else if( iRandom < ubAdultMalePercentage )
754 (*pubNumAdultMales)++;
755 else
756 (*pubNumAdultFemales)++;
757 }
758 }
759
DetermineCreatureTownCompositionBasedOnTacticalInformation(UINT8 * pubNumCreatures,UINT8 * pubNumYoungMales,UINT8 * pubNumYoungFemales,UINT8 * pubNumAdultMales,UINT8 * pubNumAdultFemales)760 void DetermineCreatureTownCompositionBasedOnTacticalInformation(UINT8 *pubNumCreatures,
761 UINT8 *pubNumYoungMales, UINT8 *pubNumYoungFemales,
762 UINT8 *pubNumAdultMales, UINT8 *pubNumAdultFemales)
763 {
764 SECTORINFO *pSector;
765
766 pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ];
767 *pubNumCreatures = 0;
768 pSector->ubNumCreatures = 0;
769 pSector->ubCreaturesInBattle = 0;
770 CFOR_EACH_IN_TEAM(s, CREATURE_TEAM)
771 {
772 if (s->bInSector && s->bLife)
773 {
774 switch (s->ubBodyType)
775 {
776 case ADULTFEMALEMONSTER:
777 (*pubNumCreatures)++;
778 (*pubNumAdultFemales)++;
779 break;
780 case AM_MONSTER:
781 (*pubNumCreatures)++;
782 (*pubNumAdultMales)++;
783 break;
784 case YAF_MONSTER:
785 (*pubNumCreatures)++;
786 (*pubNumYoungFemales)++;
787 break;
788 case YAM_MONSTER:
789 (*pubNumCreatures)++;
790 (*pubNumYoungMales)++;
791 break;
792 }
793 }
794 }
795 }
796
797
798
PrepareCreaturesForBattle()799 BOOLEAN PrepareCreaturesForBattle()
800 {
801 UNDERGROUND_SECTORINFO *pSector;
802 INT32 i, iRandom;
803 BOOLEAN fQueen;
804 UINT8 ubLarvaePercentage;
805 UINT8 ubInfantPercentage;
806 UINT8 ubYoungMalePercentage;
807 UINT8 ubYoungFemalePercentage;
808 UINT8 ubAdultMalePercentage;
809 UINT8 ubAdultFemalePercentage;
810 UINT8 ubCreatureHabitat;
811 UINT8 ubNumLarvae = 0;
812 UINT8 ubNumInfants = 0;
813 UINT8 ubNumYoungMales = 0;
814 UINT8 ubNumYoungFemales = 0;
815 UINT8 ubNumAdultMales = 0;
816 UINT8 ubNumAdultFemales = 0;
817 UINT8 ubNumCreatures;
818
819 if( !gubCreatureBattleCode )
820 {
821 //By default, we only play creature music in the cave levels (the creature levels all consistently
822 //have blue lights while human occupied mines have red lights. We always play creature music
823 //when creatures are in the level.
824 gfUseCreatureMusic = LightGetColor()->b != 0;
825
826 if( !gbWorldSectorZ )
827 return FALSE; //Creatures don't attack overworld with this battle code.
828 pSector = FindUnderGroundSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
829 if( !pSector )
830 {
831 return FALSE;
832 }
833 if( !pSector->ubNumCreatures )
834 {
835 return FALSE;
836 }
837 gfUseCreatureMusic = TRUE; //creatures are here, so play creature music
838 ubCreatureHabitat = pSector->ubCreatureHabitat;
839 ubNumCreatures = pSector->ubNumCreatures;
840 }
841 else
842 { //creatures are attacking a town sector
843 gfUseCreatureMusic = TRUE;
844 SetMusicMode( MUSIC_TACTICAL_NOTHING );
845 ubCreatureHabitat = MINE_EXIT;
846 ubNumCreatures = gubNumCreaturesAttackingTown;
847 }
848
849 switch( ubCreatureHabitat )
850 {
851 case QUEEN_LAIR:
852 fQueen = TRUE;
853 ubLarvaePercentage = 20;
854 ubInfantPercentage = 40;
855 ubYoungMalePercentage = 0;
856 ubYoungFemalePercentage = 0;
857 ubAdultMalePercentage = 30;
858 ubAdultFemalePercentage = 10;
859 break;
860 case LAIR:
861 fQueen = FALSE;
862 ubLarvaePercentage = 15;
863 ubInfantPercentage = 35;
864 ubYoungMalePercentage = 10;
865 ubYoungFemalePercentage = 5;
866 ubAdultMalePercentage = 25;
867 ubAdultFemalePercentage = 10;
868 break;
869 case LAIR_ENTRANCE:
870 fQueen = FALSE;
871 ubLarvaePercentage = 0;
872 ubInfantPercentage = 15;
873 ubYoungMalePercentage = 30;
874 ubYoungFemalePercentage = 10;
875 ubAdultMalePercentage = 35;
876 ubAdultFemalePercentage = 10;
877 break;
878 case INNER_MINE:
879 fQueen = FALSE;
880 ubLarvaePercentage = 0;
881 ubInfantPercentage = 0;
882 ubYoungMalePercentage = 20;
883 ubYoungFemalePercentage = 40;
884 ubAdultMalePercentage = 10;
885 ubAdultFemalePercentage = 30;
886 break;
887 case OUTER_MINE:
888 case MINE_EXIT:
889 fQueen = FALSE;
890 ubLarvaePercentage = 0;
891 ubInfantPercentage = 0;
892 ubYoungMalePercentage = 10;
893 ubYoungFemalePercentage = 65;
894 ubAdultMalePercentage = 5;
895 ubAdultFemalePercentage = 20;
896 break;
897 default:
898 SLOGE("Invalid creature habitat ID of %d for PrepareCreaturesForBattle. Ignoring...", ubCreatureHabitat );
899 return FALSE;
900 }
901
902 //First step is to convert the percentages into the numbers we will use.
903 if( fQueen )
904 {
905 ubNumCreatures--;
906 }
907 ubInfantPercentage += ubLarvaePercentage;
908 ubYoungMalePercentage += ubInfantPercentage;
909 ubYoungFemalePercentage += ubYoungMalePercentage;
910 ubAdultMalePercentage += ubYoungFemalePercentage;
911 ubAdultFemalePercentage += ubAdultMalePercentage;
912 if( ubAdultFemalePercentage != 100 )
913 {
914 SLOGA("Percentage for adding creatures don't add up to 100." );
915 }
916 //Second step is to determine the breakdown of the creatures randomly.
917 i = ubNumCreatures;
918 while( i-- )
919 {
920 iRandom = Random( 100 );
921 if( iRandom < ubLarvaePercentage )
922 ubNumLarvae++;
923 else if( iRandom < ubInfantPercentage )
924 ubNumInfants++;
925 else if( iRandom < ubYoungMalePercentage )
926 ubNumYoungMales++;
927 else if( iRandom < ubYoungFemalePercentage )
928 ubNumYoungFemales++;
929 else if( iRandom < ubAdultMalePercentage )
930 ubNumAdultMales++;
931 else
932 ubNumAdultFemales++;
933 }
934
935 if( gbWorldSectorZ )
936 {
937 UNDERGROUND_SECTORINFO *pUndergroundSector;
938 pUndergroundSector = FindUnderGroundSector( gWorldSectorX, gWorldSectorY, gbWorldSectorZ );
939 if( !pUndergroundSector )
940 { //No info?!!!!!
941 SLOGA("Please report underground sector you are in or going to and send save if possible." );
942 return FALSE;
943 }
944 pUndergroundSector->ubCreaturesInBattle = pUndergroundSector->ubNumCreatures;
945 }
946 else
947 {
948 SECTORINFO *pSector;
949 pSector = &SectorInfo[ SECTOR( gWorldSectorX, gWorldSectorY ) ];
950 pSector->ubNumCreatures = ubNumCreatures;
951 pSector->ubCreaturesInBattle = ubNumCreatures;
952 }
953
954 switch( gubCreatureBattleCode )
955 {
956 case CREATURE_BATTLE_CODE_NONE: //in the mines
957 AddSoldierInitListCreatures( fQueen, ubNumLarvae, ubNumInfants,
958 ubNumYoungMales, ubNumYoungFemales, ubNumAdultMales, ubNumAdultFemales );
959 break;
960 case CREATURE_BATTLE_CODE_TACTICALLYADD: //creature attacking a town sector
961 case CREATURE_BATTLE_CODE_PREBATTLEINTERFACE:
962 AddCreaturesToBattle( ubNumYoungMales, ubNumYoungFemales, ubNumAdultMales, ubNumAdultFemales );
963 break;
964 case CREATURE_BATTLE_CODE_AUTORESOLVE:
965 return FALSE;
966 }
967 return TRUE;
968 }
969
CreatureNightPlanning()970 void CreatureNightPlanning()
971 { //Check the populations of the mine exits, and factor a chance for them to attack at night.
972 for (auto lair: GCM->getCreatureLairs())
973 {
974 auto mine = GCM->getMine(lair->associatedMineId);
975 UINT8 ubNumCreatures = CreaturesInUndergroundSector(mine->entranceSector, 1);
976 if (ubNumCreatures > 1 && ubNumCreatures * 10 > (INT32)PreRandom(100))
977 { //10% chance for each creature to decide it's time to attack.
978 AddStrategicEvent(EVENT_CREATURE_ATTACK, GetWorldTotalMin() + 1 + PreRandom(429), mine->entranceSector);
979 }
980 }
981 }
982
983
CheckConditionsForTriggeringCreatureQuest(INT16 sSectorX,INT16 sSectorY,INT8 bSectorZ)984 void CheckConditionsForTriggeringCreatureQuest( INT16 sSectorX, INT16 sSectorY, INT8 bSectorZ )
985 {
986 UINT8 ubValidMines = 0;
987 if( !gGameOptions.fSciFi )
988 return; //No scifi, no creatures...
989 if( giLairID )
990 return; //Creature quest already begun
991
992 //Count the number of "infectible mines" the player occupies
993 for (auto lair : GCM->getCreatureLairs())
994 {
995 auto mine = GCM->getMine(lair->associatedMineId);
996 auto sectorIndex = SECTOR_INFO_TO_STRATEGIC_INDEX(mine->entranceSector);
997 if (!StrategicMap[sectorIndex].fEnemyControlled)
998 {
999 ubValidMines++;
1000 }
1001 }
1002
1003 if( ubValidMines >= 3 )
1004 {
1005 InitCreatureQuest();
1006 }
1007 }
1008
1009
SaveCreatureDirectives(HWFILE const hFile)1010 void SaveCreatureDirectives(HWFILE const hFile)
1011 {
1012 FileWrite(hFile, &giHabitatedDistance, 4);
1013 FileWrite(hFile, &giPopulationModifier, 4);
1014 FileWrite(hFile, &giLairID, 4);
1015 FileWrite(hFile, &gfUseCreatureMusic, 1);
1016 FileWrite(hFile, &giDestroyedLairID, 4);
1017 }
1018
1019
LoadCreatureDirectives(HWFILE const hFile,UINT32 const uiSavedGameVersion)1020 void LoadCreatureDirectives(HWFILE const hFile, UINT32 const uiSavedGameVersion)
1021 {
1022 FileRead(hFile, &giHabitatedDistance, 4);
1023 FileRead(hFile, &giPopulationModifier, 4);
1024 FileRead(hFile, &giLairID, 4);
1025 FileRead(hFile, &gfUseCreatureMusic, 1);
1026
1027 if( uiSavedGameVersion >= 82 )
1028 {
1029 FileRead(hFile, &giDestroyedLairID, 4);
1030 }
1031 else
1032 {
1033 giDestroyedLairID = 0;
1034 }
1035
1036 switch( giLairID )
1037 {
1038 case -1: //creature quest finished -- it's okay
1039 case 0: //lair doesn't exist yet -- it's okay
1040 break;
1041 default:
1042 auto lair = GCM->getCreatureLair(giLairID);
1043 if (!lair)
1044 {
1045 STLOGE("Invalid restoration of creature lair ID of {}. Save game potentially hosed.", giLairID);
1046 break;
1047 }
1048 InitCreatureLair(lair);
1049 break;
1050 }
1051 }
1052
1053
PlayerGroupIsInACreatureInfestedMine()1054 BOOLEAN PlayerGroupIsInACreatureInfestedMine()
1055 {
1056 CREATURE_DIRECTIVE *curr;
1057 INT16 sSectorX, sSectorY;
1058 INT8 bSectorZ;
1059
1060 if( giLairID <= 0 )
1061 { //Creature quest inactive
1062 return FALSE;
1063 }
1064
1065 //Lair is active, so look for live soldier in any creature level
1066 curr = gLair;
1067 while( curr )
1068 {
1069 sSectorX = curr->pLevel->ubSectorX;
1070 sSectorY = curr->pLevel->ubSectorY;
1071 bSectorZ = (INT8)curr->pLevel->ubSectorZ;
1072 //Loop through all the creature directives (mine sectors that are infectible) and
1073 //see if players are there.
1074 CFOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
1075 {
1076 if (pSoldier->bLife != 0 &&
1077 pSoldier->sSectorX == sSectorX &&
1078 pSoldier->sSectorY == sSectorY &&
1079 pSoldier->bSectorZ == bSectorZ &&
1080 !pSoldier->fBetweenSectors )
1081 {
1082 return TRUE;
1083 }
1084 }
1085 curr = curr->next;
1086 }
1087
1088 //Lair is active, but no mercs are in these sectors
1089 return FALSE;
1090 }
1091
1092
GetWarpOutOfMineCodes(INT16 * const sector_x,INT16 * const sector_y,INT8 * const sector_z,INT16 * const insertion_grid_no)1093 bool GetWarpOutOfMineCodes(INT16* const sector_x, INT16* const sector_y, INT8* const sector_z, INT16* const insertion_grid_no)
1094 {
1095 if (!gfWorldLoaded) return false;
1096 if (gbWorldSectorZ == 0) return false;
1097
1098 auto lair = gLairModel;
1099 if (lair == NULL && giLairID == -1)
1100 {
1101 // Quest is finished
1102 lair = GCM->getCreatureLair(giDestroyedLairID);
1103 }
1104
1105 // Now make sure the mercs are in the previously infested mine
1106 if (lair != NULL && lair->isSectorInLair(gWorldSectorX, gWorldSectorY, gbWorldSectorZ))
1107 {
1108 *sector_x = SECTORX(lair->warpExitSector);
1109 *sector_y = SECTORY(lair->warpExitSector);
1110 *sector_z = 0;
1111 *insertion_grid_no = lair->warpExitGridNo;
1112
1113 return true;
1114 }
1115 return false;
1116 }
1117