1 /*
2 BStone: A Source port of
3 Blake Stone: Aliens of Gold and Blake Stone: Planet Strike
4
5 Copyright (c) 1992-2013 Apogee Entertainment, LLC
6 Copyright (c) 2013-2015 Boris I. Bendovsky (bibendovsky@hotmail.com)
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the
20 Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24
25 #include "3d_def.h"
26 #include "bstone_generic_fizzle_fx.h"
27
28
29 /*
30 =============================================================================
31
32 LOCAL CONSTANTS
33
34 =============================================================================
35 */
36
37 #define LOCATION_TEXT_COLOR (0xAF)
38
39 extern char prep_msg[];
40 extern int8_t LS_current;
41 extern int8_t LS_total;
42
43
44 void Died();
45
46 void PM_SetMainMemPurge(
47 int16_t level);
48
49 void InitGoldsternInfo();
50 void InitDoorList();
51 void InitStaticList();
52 void ConnectBarriers();
53 void DrawHealth();
54 void DrawKeys();
55 void DrawWeapon();
56 void DrawScore();
57 void InitInfoArea();
58 void ForceUpdateStatusBar();
59 void UpdateStatusBar();
60
61 bool LoadLevel(
62 int levelnum);
63
64 void SetPlaneViewSize();
65
66 int16_t CalcAngle(
67 objtype* from_obj,
68 objtype* to_obj);
69
70 void FinishPaletteShifts();
71
72 void CA_CacheScreen(
73 int16_t chunk);
74
75 void VH_UpdateScreen();
76
77 void DoActor(
78 objtype* ob);
79
80 bool LevelInPlaytemp(
81 int level_index);
82
83 void PreloadUpdate(
84 uint16_t current,
85 uint16_t total);
86
87 void PreloadGraphics();
88
89 bool SaveLevel(
90 int level_index);
91
92 void CheckHighScore(
93 int32_t score,
94 uint16_t other);
95
96
97 /*
98 =============================================================================
99
100 GLOBAL VARIABLES
101
102 =============================================================================
103 */
104
105 fargametype gamestuff;
106 gametype gamestate;
107 bool ingame;
108 bool fizzlein;
109 int latchpics[NUMLATCHPICS];
110 eaWallInfo eaList[MAXEAWALLS];
111 int8_t NumEAWalls;
112
113 tilecoord_t GoldieList[GOLDIE_MAX_SPAWNS];
114 GoldsternInfo_t GoldsternInfo;
115
116 extern uint16_t scan_value;
117
118 int NUMWEAPONS = 0;
119
120
121 void ScanInfoPlane();
122 void SetupGameLevel();
123
124 void DrawPlayScreen(
125 bool InitInfoMsg);
126
127 void LoadLatchMem();
128 void GameLoop();
129
130 // BBi
131 static void fix_level_inplace();
132
133
134 /*
135 =============================================================================
136
137 LOCAL VARIABLES
138
139 =============================================================================
140 */
141
142 //
143 // NOTE: This array indexs the "statinfo" array in ACT1.C and is indexed
144 // upon tile number/values.
145 //
146
147 int8_t ExpCrateShapes[] = {
148 42, // Chicken Leg
149 44, // Ham/Steak
150 26, // Clip
151 24, // Pistol
152 27, // Pulse
153 28, // ION
154 46, // Grenade
155 62, // Money Bag
156 63, // Loot
157 64, // Gold
158 65, // Bonus
159 71, // Gore 1
160 74, // Gore 2
161 32, // red key
162 33, // yel key
163 34, // grn key
164 35, // blu key
165 36, // gld key
166 };
167
168
169 /*
170 ==========================
171 =
172 = SetSoundLoc - Given the location of an object (in terms of global
173 = coordinates, held in globalsoundx and globalsoundy), munges the values
174 = for an approximate distance from the left and right ear, and puts
175 = those values into leftchannel and rightchannel.
176 =
177 = JAB
178 =
179 ==========================
180 */
181
182 fixed globalsoundx;
183 fixed globalsoundy;
184 int16_t leftchannel;
185 int16_t rightchannel;
186
187 #define ATABLEMAX (15)
188
189 uint8_t righttable[ATABLEMAX][ATABLEMAX * 2] = {
190 { 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 6, 0, 0, 0, 0, 0, 1, 3, 5, 8, 8, 8, 8, 8, 8, 8, 8 },
191 { 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 7, 6, 4, 0, 0, 0, 0, 0, 2, 4, 6, 8, 8, 8, 8, 8, 8, 8, 8 },
192 { 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 4, 1, 0, 0, 0, 1, 2, 4, 6, 8, 8, 8, 8, 8, 8, 8, 8 },
193 { 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 7, 6, 5, 4, 2, 1, 0, 1, 2, 3, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 },
194 { 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 6, 5, 4, 3, 2, 2, 3, 3, 5, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
195 { 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 4, 4, 4, 4, 5, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
196 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 6, 6, 5, 5, 5, 6, 6, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
197 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7, 6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
198 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
199 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
200 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
201 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
202 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
203 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
204 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }
205 };
206
207 uint8_t lefttable[ATABLEMAX][ATABLEMAX * 2] = {
208 { 8, 8, 8, 8, 8, 8, 8, 8, 5, 3, 1, 0, 0, 0, 0, 0, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8 },
209 { 8, 8, 8, 8, 8, 8, 8, 8, 6, 4, 2, 0, 0, 0, 0, 0, 4, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8 },
210 { 8, 8, 8, 8, 8, 8, 8, 8, 6, 4, 2, 1, 0, 0, 0, 1, 4, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8 },
211 { 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 3, 2, 1, 0, 1, 2, 4, 5, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8 },
212 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 6, 5, 3, 3, 2, 2, 3, 4, 5, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8 },
213 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 6, 5, 4, 4, 4, 4, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8 },
214 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 6, 6, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
215 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 6, 6, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
216 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
217 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
218 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
219 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
220 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
221 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 },
222 { 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }
223 };
224
SetSoundLoc(fixed gx,fixed gy)225 void SetSoundLoc(
226 fixed gx,
227 fixed gy)
228 {
229 fixed xt, yt;
230 int16_t x, y;
231
232 //
233 // translate point to view centered coordinates
234 //
235 gx -= viewx;
236 gy -= viewy;
237
238 //
239 // calculate newx
240 //
241 xt = FixedByFrac(gx, viewcos);
242 yt = FixedByFrac(gy, viewsin);
243 x = (xt - yt) >> TILESHIFT;
244
245 //
246 // calculate newy
247 //
248 xt = FixedByFrac(gx, viewsin);
249 yt = FixedByFrac(gy, viewcos);
250 y = (yt + xt) >> TILESHIFT;
251
252 if (y >= ATABLEMAX) {
253 y = ATABLEMAX - 1;
254 } else if (y <= -ATABLEMAX) {
255 y = -ATABLEMAX;
256 }
257 if (x < 0) {
258 x = -x;
259 }
260 if (x >= ATABLEMAX) {
261 x = ATABLEMAX - 1;
262 }
263 leftchannel = lefttable[x][y + ATABLEMAX];
264 rightchannel = righttable[x][y + ATABLEMAX];
265 }
266
267 /*
268 ==========================
269 =
270 = SetSoundLocGlobal - Sets up globalsoundx & globalsoundy and then calls
271 = UpdateSoundLoc() to transform that into relative channel volumes. Those
272 = values are then passed to the Sound Manager so that they'll be used for
273 = the next sound played (if possible).
274 =
275 = JAB
276 =
277 ==========================
278 */
279
UpdateSoundLoc()280 void UpdateSoundLoc()
281 {
282 ::sd_update_positions();
283 }
284
285 /*
286 ** JAB End
287 */
288
289
ClearMemory()290 void ClearMemory()
291 {
292 }
293
294
295 #define INVALID_ACTOR_ERR Quit("Invalid actor: {} {}", x, y)
296
297
298 /*
299 ==========================
300 =
301 = ScanInfoPlane
302 =
303 = Spawn all actors and mark down special places
304 =
305 ==========================
306 */
ScanInfoPlane()307 void ScanInfoPlane()
308 {
309 uint16_t x, y;
310 int16_t tile;
311 uint16_t* start;
312 bool gottextures = false;
313 bool gotcolors = false;
314
315 detonators_spawned = 0;
316
317 new_actor = nullptr;
318 start = mapsegs[1];
319 for (y = 0; y < mapheight; y++) {
320 for (x = 0; x < mapwidth; x++) {
321 sci_mCacheInfo* ci;
322 scientist_t* st = nullptr;
323 uint8_t tilehi, tilelo, block = 0;
324
325
326 tile = *start++;
327 //
328 // Check for tiles/icons to ignore...
329 //
330 switch ((uint16_t) * (mapsegs[0] + farmapylookup[y] + x)) {
331 case SMART_OFF_TRIGGER:
332 case SMART_ON_TRIGGER:
333 if (!::is_ps()) {
334 ::Quit("Smart trigger (PS) at ({}, {}).", x, y);
335 }
336 continue;
337
338 case DOORTRIGGERTILE:
339 // Ignore all values/icons on top of these tiles...
340 continue;
341 }
342 tilehi = (tile & 0xff00) >> 8;
343 tilelo = (tile & 0xff);
344
345 if (y < 63 && x < 63 && (*start & 0xff00) == 0xfa00) {
346 scan_value = *start & 0x00ff;
347 } else {
348 scan_value = 0xffff;
349 }
350
351 switch (tilehi) {
352 case 0xfe: // Top/Bottom colors
353 if (gotcolors) {
354 break;
355 }
356 x++;
357 tile = *start++;
358 TopColor = tile & 0xff00;
359 TopColor |= TopColor >> 8;
360 BottomColor = tile & 0xff;
361 BottomColor |= BottomColor << 8;
362 gotcolors = true;
363 continue;
364 break;
365
366 case 0xFB: // Global Ceiling/Floor textures
367 if (gottextures) {
368 break;
369 }
370 x++;
371 tile = *start++;
372
373 CeilingTile = START_TEXTURES + ((tile & 0xff00) >> 8);
374 if (CeilingTile > NUM_TILES - 1) {
375 ::Quit("Ceiling tile/texture is out of range.");
376 }
377
378 FloorTile = START_TEXTURES + (tile & 0xff);
379 if (FloorTile > NUM_TILES - 1) {
380 ::Quit("Floor tile/texture is out of range.");
381 }
382
383 gottextures = true;
384 continue;
385 break;
386
387
388 case 0xf5: // IntraLevel warp
389 *(start - 1) = *start; // Move Coord right on top
390 *start = 0;
391 continue;
392 break;
393
394 case 0xfa:
395 continue;
396
397 case 0xf1: // Informant messages
398 case 0xf2: // "Nice" scientist messages
399 case 0xf3: // "Mean" scientist messages
400 switch (tilehi) {
401 case 0xf1:
402 block = static_cast<uint8_t>(INFORMANT_HINTS);
403 st = &InfHintList;
404 break;
405
406 case 0xf2:
407 block = static_cast<uint8_t>(NICE_SCIE_HINTS);
408 st = &NiceSciList;
409 break;
410
411 case 0xf3:
412 block = static_cast<uint8_t>(MEAN_SCIE_HINTS);
413 st = &MeanSciList;
414 break;
415 }
416
417 ci = &st->smInfo[st->NumMsgs];
418 ci->mInfo.local_val = 0xff;
419 ci->mInfo.global_val = tilelo;
420 if (!ReuseMsg((mCacheInfo*)ci, st->NumMsgs, sizeof(sci_mCacheInfo))) {
421 CacheMsg((mCacheInfo*)ci, block, ci->mInfo.global_val);
422 ci->mInfo.local_val = static_cast<uint8_t>(InfHintList.NumMsgs);
423 }
424
425 if (++st->NumMsgs > MAX_CACHE_MSGS) {
426 ::Quit("(INFORMANTS) Too many 'cached msgs' loaded.");
427 }
428
429 ci->areanumber = GetAreaNumber(static_cast<int8_t>(x), static_cast<int8_t>(y));
430
431 if (ci->areanumber == 0 || ci->areanumber >= NUMAREAS) {
432 ci->areanumber = 0xff;
433 }
434 continue;
435 break;
436
437 case 0:
438 if (!tilelo) {
439 continue;
440 }
441 break;
442 }
443
444 //
445 // SPECIAL SPAWN CODING FOR BLASTABLE CRATES...
446 //
447
448 if (tile >= 432 && tile <= 485) {
449 if (!::is_aog_sw() && tile >= 468) {
450 SpawnOffsetObj(en_crate3, x, y);
451 new_actor->temp2 = ExpCrateShapes[tile - 468];
452 new_actor->temp3 = static_object_to_ui16(ReserveStatic());
453
454 if (tile >= 475 && tile <= 478) {
455 tile = (tile - 475) + bo_money_bag;
456 } else {
457 tile = 0;
458 }
459 } else if (!::is_aog_sw() && tile >= 450) {
460 SpawnOffsetObj(en_crate2, x, y);
461 new_actor->temp2 = ExpCrateShapes[tile - 450];
462 new_actor->temp3 = static_object_to_ui16(ReserveStatic());
463
464 if (tile >= 457 && tile <= 460) {
465 tile = (tile - 457) + bo_money_bag;
466 } else {
467 tile = 0;
468 }
469 } else if (tile >= 432) {
470 SpawnOffsetObj(en_crate1, x, y);
471 new_actor->temp2 = ExpCrateShapes[tile - 432];
472 new_actor->temp3 = static_object_to_ui16(ReserveStatic());
473
474 if (tile >= 439 && tile <= 442) {
475 tile = (tile - 439) + bo_money_bag;
476 } else {
477 tile = 0;
478 }
479 }
480
481 if (tile) {
482 if (tile > bo_loot) {
483 tile += 3;
484 }
485 tile -= bo_money_bag;
486 AddTotalPoints(static_points[tile]);
487 }
488
489 continue;
490 }
491
492 switch (tile) {
493 case 19:
494 case 20:
495 case 21:
496 case 22:
497 SpawnPlayer(x, y, NORTH + tile - 19);
498 break;
499
500 case 30: // Yellow Puddle
501 if (::is_aog_sw()) {
502 ::Quit("Yellow puddle (AOG full/PS) at ({}, {}).", x, y);
503 }
504 SpawnStatic(x, y, tile - 23);
505 break;
506
507 case 71: // BFG Weapon
508 if (!::is_ps()) {
509 ::Quit("BFG (PS) at ({}, {}).", x, y);
510 }
511 SpawnStatic(x, y, tile - 23);
512 break;
513
514 case 85: // Money bag
515 case 86: // Loot
516 case 87: // Gold
517 case 88: // Bonus
518 AddTotalPoints(static_points[statinfo[tile - 23].type - bo_money_bag]);
519
520 case 53:
521
522 case 23:
523 case 24:
524 case 25:
525 case 26:
526 case 27:
527 case 28:
528 case 29:
529
530 case 31:
531 case 32:
532 case 33:
533 case 34:
534 case 35:
535 case 36:
536 case 37:
537 case 38:
538
539 case 39:
540 case 40:
541 case 41:
542 case 42:
543 case 43:
544 case 44:
545 case 45:
546 case 46:
547
548 case 47:
549 case 48:
550 case 49:
551 case 50:
552 case 51:
553 case 52:
554 case 54:
555
556 case 55:
557 case 56:
558 case 57:
559 case 58:
560 case 59:
561 case 60:
562 case 61:
563 case 62:
564
565 case 63:
566 case 64:
567 case 65:
568 case 66:
569 case 67:
570 case 68:
571 case 69:
572 case 70:
573
574 case 72: // Gurney Mutant
575 case 73: // Large Canister
576 case 74: // Small Canister
577 case 75: // Empty Gurney
578 case 76: // Empty Large Canister
579 case 77: // Empty Small Canister
580 case 78: // Dead Gen. Sci.
581
582 case 80:
583 case 83: // Floor Grate
584 case 84: // Floor Pipe
585 SpawnStatic(x, y, tile - 23);
586 break;
587
588 case 399: // gold 1
589 case 400: // gold 2
590 case 401: // gold 3
591 AddTotalPoints(static_points[statinfo[tile - 315].type - bo_money_bag]);
592
593 case 381:
594 case 382:
595 case 383:
596 case 384:
597 case 385:
598 case 386:
599 case 387:
600 case 388:
601 case 390: // candy bar
602 case 391: // sandwich
603
604 case 395: // Table
605 case 396: // Chair
606 case 397: // Stool
607 case 398: // Gore
608
609 case 402: //
610 case 403: //
611 case 404: //
612 case 405: //
613 case 406: //
614 case 407: //
615 case 408: //
616 case 409: //
617 case 410: //
618 case 411: //
619 case 412: //
620 case 413: //
621 case 414: //
622 case 415: //
623 case 416: //
624 case 417: //
625 case 418: //
626 case 419: //
627 case 420: //
628 case 421: //
629 case 422: //
630 case 423: // bo_coin
631 case 424: // bo_coin5
632 SpawnStatic(x, y, tile - 315);
633 break;
634
635 case 486: // Plasma Detonator
636 if (!::is_ps()) {
637 ::Quit("Plasma detonator (PS) at ({}, {}).", x, y);
638 }
639
640 SpawnHiddenOfs(en_plasma_detonator_reserve, x, y); // Spawn a reserve
641 SpawnStatic(x, y, 486 - 375);
642 break;
643
644 case 487: // Door rubble
645 case 488: // AutoMapper Bonus #1
646 case 489: // BonziTree
647 case 490: // Yellow Potted plant
648 case 491: // Tube Plant
649 case 492: // HiTech Chair
650 case 493: // AOG: Rent A Cop - Dead.
651 case 494: // AOG: Pro Guard - Dead.
652 case 495: // AOG: Swat Guard - Dead.
653 SpawnStatic(x, y, tile - 375);
654 break;
655
656
657 case 393: // crate 2
658 case 394: // crate 3
659 case 392: // crate 1
660 SpawnStatic(x, y, tile - 315);
661 break;
662
663 case 81:
664 case 82:
665 SpawnOffsetObj(static_cast<enemy_t>(en_bloodvent + tile - 81), x, y);
666 break;
667
668
669 //
670 // GREEN OOZE
671 //
672
673 case 208:
674 if (gamestate.difficulty < gd_hard) {
675 break;
676 }
677 case 207:
678 if (gamestate.difficulty < gd_medium) {
679 break;
680 }
681 case 206:
682 SpawnOffsetObj(en_green_ooze, x, y);
683 break;
684
685
686 //
687 // BLACK OOZE
688 //
689
690 case 212:
691 if (gamestate.difficulty < gd_hard) {
692 break;
693 }
694 case 211:
695 if (gamestate.difficulty < gd_medium) {
696 break;
697 }
698 case 210:
699 SpawnOffsetObj(en_black_ooze, x, y);
700 break;
701
702
703
704 // Flickering Light
705 //
706 case 79:
707 SpawnOffsetObj(en_flickerlight, x, y);
708 new_actor->lighting = LAMP_ON_SHADING;
709 break;
710
711
712 case 174:
713 if (::is_ps()) {
714 ::SpawnBarrier(en_post_barrier, x, y, false);
715 } else {
716 ::SpawnBarrier(en_arc_barrier, x, y, true);
717 }
718 break;
719
720 case 175:
721 //
722 // 174=off,175=on
723 //
724 SpawnBarrier(en_post_barrier, x, y, (tile - 174) != 0);
725 break;
726
727 case 138:
728 case 139:
729 if (!::is_ps()) {
730 ::Quit("Switchable arc barrier (PS) at ({}, {}).", x, y);
731 }
732
733 //
734 // 138=off,139=on
735 //
736 SpawnBarrier(en_arc_barrier, x, y, (tile - 138) != 0);
737 break;
738
739 //
740 // VPOST Barrier
741 //
742
743 //
744 // Switchable
745 //
746 case 563: // On
747 case 562: // Off
748 if (!::is_ps()) {
749 ::Quit("Switchable post barrier (PS) at ({}, {}).", x, y);
750 }
751
752 SpawnBarrier(en_vpost_barrier, x, y, (tile - 562) != 0);
753 break;
754
755
756 //
757 // Cycle
758 //
759 case 567:
760 if (gamestate.difficulty < gd_hard) {
761 break;
762 }
763 case 566:
764 if (gamestate.difficulty < gd_medium) {
765 break;
766 }
767 case 565:
768 if (!::is_ps()) {
769 ::Quit("Cyclic post barrier (PS) at ({}, {}).", x, y);
770 }
771
772 SpawnBarrier(en_vpost_barrier, x, y, 0);
773 break;
774
775 //
776 // VSPIKE Barrier
777 //
778
779 //
780 // Switchable
781 //
782 case 426: // On
783 case 425: // Off
784 if (!::is_ps()) {
785 ::Quit("Spike barrier (PS) at ({}, {}).", x, y);
786 }
787
788 SpawnBarrier(en_vspike_barrier, x, y, (tile - 425) != 0);
789 break;
790
791
792 //
793 // Cycle
794 //
795 case 430:
796 if (gamestate.difficulty < gd_hard) {
797 break;
798 }
799 case 429:
800 if (gamestate.difficulty < gd_medium) {
801 break;
802 }
803 case 428:
804 if (!::is_ps()) {
805 ::Quit("Cyclic spike barrier (PS) at ({}, {}).", x, y);
806 }
807 SpawnBarrier(en_vspike_barrier, x, y, 0);
808 break;
809
810 //
811 // STEAM GRATE
812 //
813
814 case 178:
815 SpawnStand(en_steamgrate, x, y, 0);
816 break;
817
818 //
819 // STEAM PIPE
820 //
821
822 case 179:
823 SpawnStand(en_steampipe, x, y, 0);
824 break;
825
826
827 //
828 // GOLDFIRE SPAWN SITES
829 //
830 case 124:
831 if (!loadedgame) {
832 if (GoldsternInfo.SpawnCnt == GOLDIE_MAX_SPAWNS) {
833 ::Quit("Too many Dr. Goldfire Spawn sites in level.");
834 }
835 GoldsternInfo.flags = GS_FIRSTTIME;
836 if (gamestate.mapon == 9) {
837 GoldsternInfo.WaitTime = 60;
838 } else {
839 GoldsternInfo.WaitTime = MIN_GOLDIE_FIRST_WAIT + Random(MAX_GOLDIE_FIRST_WAIT - MIN_GOLDIE_FIRST_WAIT);
840 }
841 GoldieList[GoldsternInfo.SpawnCnt].tilex = static_cast<uint8_t>(x);
842 GoldieList[GoldsternInfo.SpawnCnt].tiley = static_cast<uint8_t>(y);
843 GoldsternInfo.SpawnCnt++;
844
845 if (::is_ps() && gamestate.mapon == GOLD_MORPH_LEVEL) {
846 AddTotalPoints(actor_points[goldsternobj - rentacopobj]);
847 AddTotalEnemy(1);
848 }
849 }
850 break;
851
852 //
853 // GOLDFIRE SPAWN - IMMEDEATLY
854 //
855
856 case 141:
857 if (!::is_ps()) {
858 ::Quit("Goldstern spawn (PS) at ({}, {}).", x, y);
859 }
860
861 if (!loadedgame) {
862 if (GoldsternInfo.GoldSpawned) {
863 ::Quit("Too many FAST Goldfire spawn sites in map.");
864 }
865
866 if (GoldsternInfo.SpawnCnt == GOLDIE_MAX_SPAWNS) {
867 ::Quit("Too many Dr. Goldfire Spawn sites in level.");
868 }
869
870 GoldieList[GoldsternInfo.SpawnCnt].tilex = static_cast<uint8_t>(x);
871 GoldieList[GoldsternInfo.SpawnCnt].tiley = static_cast<uint8_t>(y);
872
873 GoldsternInfo.LastIndex = GoldsternInfo.SpawnCnt++;
874 GoldsternInfo.flags = GS_COORDFOUND;
875
876 SpawnStand(en_goldstern, x, y, 0);
877 GoldsternInfo.GoldSpawned = true;
878 new_actor = nullptr;
879 }
880 break;
881
882
883 //
884 // SECURITY LIGHT
885 //
886 case 160:
887 SpawnOffsetObj(en_security_light, x, y);
888 break;
889
890
891 //
892 // Projection Generator (AOG) / Rotating Cube (PS)
893 //
894
895 case 177:
896 ::SpawnOffsetObj(en_rotating_cube, x, y);
897
898 if (::is_ps()) {
899 ::new_actor = nullptr;
900 }
901
902 break;
903
904 //
905 // RENT-A-COP
906 //
907 case 180:
908 case 181:
909 case 182:
910 case 183:
911 if (gamestate.difficulty < gd_hard) {
912 break;
913 }
914 tile -= 36;
915 case 144:
916 case 145:
917 case 146:
918 case 147:
919 if (gamestate.difficulty < gd_medium) {
920 break;
921 }
922 tile -= 36;
923 case 108:
924 case 109:
925 case 110:
926 case 111:
927 SpawnStand(en_rentacop, x, y, tile - 108);
928 break;
929
930
931 case 184:
932 case 185:
933 case 186:
934 case 187:
935 if (gamestate.difficulty < gd_hard) {
936 break;
937 }
938 tile -= 36;
939 case 148:
940 case 149:
941 case 150:
942 case 151:
943 if (gamestate.difficulty < gd_medium) {
944 break;
945 }
946 tile -= 36;
947 case 112:
948 case 113:
949 case 114:
950 case 115:
951 SpawnPatrol(en_rentacop, x, y, tile - 112);
952 break;
953
954
955 //
956 // officer
957 //
958 case 188:
959 case 189:
960 case 190:
961 case 191:
962 if (gamestate.difficulty < gd_hard) {
963 break;
964 }
965 tile -= 36;
966 case 152:
967 case 153:
968 case 154:
969 case 155:
970 if (gamestate.difficulty < gd_medium) {
971 break;
972 }
973 tile -= 36;
974 case 116:
975 case 117:
976 case 118:
977 case 119:
978 SpawnStand(en_gen_scientist, x, y, tile - 116);
979 if (new_actor->flags & FL_INFORMANT) {
980 AddTotalInformants(1);
981 new_actor = nullptr;
982 }
983 break;
984
985
986 case 192:
987 case 193:
988 case 194:
989 case 195:
990 if (gamestate.difficulty < gd_hard) {
991 break;
992 }
993 tile -= 36;
994 case 156:
995 case 157:
996 case 158:
997 case 159:
998 if (gamestate.difficulty < gd_medium) {
999 break;
1000 }
1001 tile -= 36;
1002 case 120:
1003 case 121:
1004 case 122:
1005 case 123:
1006 SpawnPatrol(en_gen_scientist, x, y, tile - 120);
1007 if (new_actor->flags & FL_INFORMANT) {
1008 AddTotalInformants(1);
1009 new_actor = nullptr;
1010 }
1011 break;
1012
1013
1014 //
1015 // PROGUARD
1016 //
1017 case 198:
1018 case 199:
1019 case 200:
1020 case 201:
1021 if (gamestate.difficulty < gd_hard) {
1022 break;
1023 }
1024 tile -= 36;
1025 case 162:
1026 case 163:
1027 case 164:
1028 case 165:
1029 if (gamestate.difficulty < gd_medium) {
1030 break;
1031 }
1032 tile -= 36;
1033 case 126:
1034 case 127:
1035 case 128:
1036 case 129:
1037 SpawnStand(en_proguard, x, y, tile - 126);
1038 break;
1039
1040
1041 case 202:
1042 case 203:
1043 case 204:
1044 case 205:
1045 if (gamestate.difficulty < gd_hard) {
1046 break;
1047 }
1048 tile -= 36;
1049 case 166:
1050 case 167:
1051 case 168:
1052 case 169:
1053 if (gamestate.difficulty < gd_medium) {
1054 break;
1055 }
1056 tile -= 36;
1057 case 130:
1058 case 131:
1059 case 132:
1060 case 133:
1061 SpawnPatrol(en_proguard, x, y, tile - 130);
1062 break;
1063
1064
1065
1066 case 312:
1067 if (gamestate.difficulty < gd_hard) {
1068 break;
1069 }
1070
1071 case 311:
1072 if (gamestate.difficulty < gd_medium) {
1073 break;
1074 }
1075
1076 case 310:
1077 SpawnStand(en_electro_alien, x, y, 0);
1078 new_actor = nullptr;
1079 break;
1080
1081
1082 //
1083 // FLOATING BOMB - Stationary
1084 //
1085
1086 case 364:
1087 case 365:
1088 case 366:
1089 case 367:
1090 if (gamestate.difficulty < gd_hard) {
1091 break;
1092 }
1093 tile -= 18;
1094 case 346:
1095 case 347:
1096 case 348:
1097 case 349:
1098 if (gamestate.difficulty < gd_medium) {
1099 break;
1100 }
1101 tile -= 18;
1102 case 328:
1103 case 329:
1104 case 330:
1105 case 331:
1106 SpawnStand(en_floatingbomb, x, y, tile - 328);
1107 new_actor->flags |= FL_STATIONARY;
1108 break;
1109
1110
1111 //
1112 // FLOATING BOMB - Start Stationary
1113 //
1114
1115 case 296:
1116 case 297:
1117 case 298:
1118 case 299:
1119 if (gamestate.difficulty < gd_hard) {
1120 break;
1121 }
1122 tile -= 18;
1123 case 278:
1124 case 279:
1125 case 280:
1126 case 281:
1127 if (gamestate.difficulty < gd_medium) {
1128 break;
1129 }
1130 tile -= 18;
1131 case 260:
1132 case 261:
1133 case 262:
1134 case 263:
1135 SpawnStand(en_floatingbomb, x, y, tile - 260);
1136 break;
1137
1138
1139 //
1140 // FLOATING BOMB - Start Moving
1141 //
1142
1143 case 300:
1144 case 301:
1145 case 302:
1146 case 303:
1147 if (gamestate.difficulty < gd_hard) {
1148 break;
1149 }
1150 tile -= 18;
1151 case 282:
1152 case 283:
1153 case 284:
1154 case 285:
1155 if (gamestate.difficulty < gd_medium) {
1156 break;
1157 }
1158 tile -= 18;
1159 case 264:
1160 case 265:
1161 case 266:
1162 case 267:
1163 SpawnPatrol(en_floatingbomb, x, y, tile - 264);
1164 break;
1165
1166
1167 //
1168 // VOLATILE MAT. TRANSPORT - Stationary
1169 //
1170 case 350:
1171 case 351:
1172 case 352:
1173 case 353:
1174 if (gamestate.difficulty < gd_hard) {
1175 break;
1176 }
1177 tile -= 18;
1178 case 332:
1179 case 333:
1180 case 334:
1181 case 335:
1182 if (gamestate.difficulty < gd_medium) {
1183 break;
1184 }
1185 tile -= 18;
1186 case 314:
1187 case 315:
1188 case 316:
1189 case 317:
1190 SpawnStand(en_volatiletransport, x, y, tile - 314);
1191 break;
1192
1193
1194 //
1195 // Black Ooze
1196 //
1197 case 313:
1198 if (!::is_ps()) {
1199 ::Quit("Black ooze (PS) at ({}, {}).", x, y);
1200 }
1201
1202 if (gamestate.difficulty < gd_hard) {
1203 break;
1204 }
1205 tile -= 18;
1206 case 295:
1207 if (!::is_ps()) {
1208 ::Quit("Black ooze (PS) at ({}, {}).", x, y);
1209 }
1210
1211 if (gamestate.difficulty < gd_medium) {
1212 break;
1213 }
1214 tile -= 18;
1215 case 277:
1216 if (!::is_ps()) {
1217 ::Quit("Black ooze (PS) at ({}, {}).", x, y);
1218 }
1219
1220 SpawnOffsetObj(en_black2_ooze, x, y);
1221 break;
1222
1223
1224
1225 //
1226 // Green Ooze
1227 //
1228 case 322:
1229 if (!::is_ps()) {
1230 ::Quit("Green ooze (PS) at ({}, {}).", x, y);
1231 }
1232
1233 if (gamestate.difficulty < gd_hard) {
1234 break;
1235 }
1236 tile -= 18;
1237 case 304:
1238 if (!::is_ps()) {
1239 ::Quit("Green ooze (PS) at ({}, {}).", x, y);
1240 }
1241
1242 if (gamestate.difficulty < gd_medium) {
1243 break;
1244 }
1245 tile -= 18;
1246 case 286:
1247 if (!::is_ps()) {
1248 ::Quit("Green ooze (PS) at ({}, {}).", x, y);
1249 }
1250
1251 SpawnOffsetObj(en_green2_ooze, x, y);
1252 break;
1253
1254
1255 //
1256 // VOLATILE MAT. TRANSPORT - Moving
1257 //
1258 case 354:
1259 case 355:
1260 case 356:
1261 case 357:
1262 if (::is_aog_sw()) {
1263 INVALID_ACTOR_ERR;
1264 }
1265
1266 if (gamestate.difficulty < gd_hard) {
1267 break;
1268 }
1269 tile -= 18;
1270 case 336:
1271 case 337:
1272 case 338:
1273 case 339:
1274 if (::is_aog_sw()) {
1275 INVALID_ACTOR_ERR;
1276 }
1277
1278 if (gamestate.difficulty < gd_medium) {
1279 break;
1280 }
1281 tile -= 18;
1282 case 318:
1283 case 319:
1284 case 320:
1285 case 321:
1286 if (::is_aog_sw()) {
1287 INVALID_ACTOR_ERR;
1288 }
1289
1290 SpawnPatrol(en_volatiletransport, x, y, tile - 318);
1291 break;
1292
1293 //
1294 // Genetic Guard
1295 //
1296
1297 case 143:
1298 if (gamestate.difficulty < gd_hard) {
1299 break;
1300 }
1301 case 142:
1302 if (gamestate.difficulty < gd_medium) {
1303 break;
1304 }
1305 case 214:
1306 SpawnOffsetObj(en_genetic_guard, x, y);
1307 break;
1308
1309
1310 //
1311 // Cyborg Warrior
1312 //
1313 case 603:
1314 if (!::is_ps()) {
1315 INVALID_ACTOR_ERR;
1316 }
1317
1318 if (gamestate.difficulty < gd_hard) {
1319 break;
1320 }
1321 case 585:
1322 if (!::is_ps()) {
1323 INVALID_ACTOR_ERR;
1324 }
1325
1326 if (gamestate.difficulty < gd_medium) {
1327 break;
1328 }
1329 case 250:
1330 SpawnOffsetObj(en_cyborg_warrior, x, y);
1331 break;
1332
1333
1334 //
1335 // Spider Mutant
1336 //
1337 case 601:
1338 if (!::is_ps()) {
1339 INVALID_ACTOR_ERR;
1340 }
1341
1342 if (gamestate.difficulty < gd_hard) {
1343 break;
1344 }
1345 case 583:
1346 if (!::is_ps()) {
1347 INVALID_ACTOR_ERR;
1348 }
1349
1350 if (gamestate.difficulty < gd_medium) {
1351 break;
1352 }
1353 case 232:
1354 SpawnOffsetObj(en_spider_mutant, x, y);
1355 break;
1356
1357 //
1358 // Acid Dragon
1359 //
1360 case 605:
1361 if (!::is_ps()) {
1362 INVALID_ACTOR_ERR;
1363 }
1364
1365 if (gamestate.difficulty < gd_hard) {
1366 break;
1367 }
1368 case 587:
1369 if (!::is_ps()) {
1370 INVALID_ACTOR_ERR;
1371 }
1372
1373 if (gamestate.difficulty < gd_medium) {
1374 break;
1375 }
1376
1377 case 268:
1378 SpawnOffsetObj(en_acid_dragon, x, y);
1379 break;
1380
1381 //
1382 // Breather beast
1383 //
1384 case 602:
1385 if (!::is_ps()) {
1386 INVALID_ACTOR_ERR;
1387 }
1388
1389 if (gamestate.difficulty < gd_hard) {
1390 break;
1391 }
1392 case 584:
1393 if (!::is_ps()) {
1394 INVALID_ACTOR_ERR;
1395 }
1396
1397 if (gamestate.difficulty < gd_medium) {
1398 break;
1399 }
1400
1401 case 233:
1402 SpawnOffsetObj(en_breather_beast, x, y);
1403 break;
1404
1405 //
1406 // Mech Guardian
1407 //
1408 case 606:
1409 if (!::is_ps()) {
1410 INVALID_ACTOR_ERR;
1411 }
1412
1413 if (gamestate.difficulty < gd_hard) {
1414 break;
1415 }
1416 case 588:
1417 if (!::is_ps()) {
1418 INVALID_ACTOR_ERR;
1419 }
1420
1421 if (gamestate.difficulty < gd_medium) {
1422 break;
1423 }
1424
1425 case 269:
1426 SpawnOffsetObj(en_mech_guardian, x, y);
1427 break;
1428
1429 //
1430 // Reptilian Warrior
1431 //
1432 case 604:
1433 if (!::is_ps()) {
1434 INVALID_ACTOR_ERR;
1435 }
1436
1437 if (gamestate.difficulty < gd_hard) {
1438 break;
1439 }
1440 case 586:
1441 if (!::is_ps()) {
1442 INVALID_ACTOR_ERR;
1443 }
1444
1445 if (gamestate.difficulty < gd_medium) {
1446 break;
1447 }
1448
1449 case 251:
1450 SpawnOffsetObj(en_reptilian_warrior, x, y);
1451 break;
1452
1453
1454 //
1455 // Mutant Human type 1
1456 //
1457
1458 case 105:
1459 if (gamestate.difficulty < gd_hard) {
1460 break;
1461 }
1462 case 104:
1463 if (gamestate.difficulty < gd_medium) {
1464 break;
1465 }
1466 case 103:
1467 SpawnOffsetObj(en_mutant_human1, x, y);
1468 break;
1469
1470
1471 //
1472 // Mutant Human type 2
1473 //
1474 case 125:
1475 if (gamestate.difficulty < gd_hard) {
1476 break;
1477 }
1478 case 107:
1479 if (gamestate.difficulty < gd_medium) {
1480 break;
1481 }
1482 case 106:
1483 SpawnOffsetObj(en_mutant_human2, x, y);
1484 break;
1485
1486
1487 //
1488 // Small Canister Alien - CONTAINED
1489 //
1490
1491 case 136:
1492 if (gamestate.difficulty < gd_hard) {
1493 SpawnStatic(x, y, 74 - 23);
1494 break;
1495 }
1496 case 135:
1497 if (gamestate.difficulty < gd_medium) {
1498 SpawnStatic(x, y, 74 - 23);
1499 break;
1500 }
1501 case 134:
1502 SpawnOffsetObj(en_scan_wait_alien, x, y);
1503 break;
1504
1505
1506
1507 //
1508 // Large Canister Alien - CONTAINED
1509 //
1510 case 172:
1511 if (gamestate.difficulty < gd_hard) {
1512 SpawnStatic(x, y, 73 - 23);
1513 break;
1514 }
1515 case 171:
1516 if (gamestate.difficulty < gd_medium) {
1517 SpawnStatic(x, y, 73 - 23);
1518 break;
1519 }
1520 case 170:
1521 SpawnOffsetObj(en_lcan_wait_alien, x, y);
1522 break;
1523
1524
1525 //
1526 // Gurney Mutant - ASLEEP
1527 //
1528
1529 case 161:
1530 if (gamestate.difficulty < gd_hard) {
1531 SpawnStatic(x, y, 72 - 23);
1532 break;
1533 }
1534 case 173:
1535 if (gamestate.difficulty < gd_medium) {
1536 SpawnStatic(x, y, 72 - 23);
1537 break;
1538 }
1539 case 137:
1540 SpawnOffsetObj(en_gurney_wait, x, y);
1541 break;
1542
1543 //
1544 // Small Canister Alien - ACTIVE/WALKING
1545 //
1546
1547 case 288:
1548 if (gamestate.difficulty < gd_hard) {
1549 break;
1550 }
1551 case 289:
1552 if (gamestate.difficulty < gd_medium) {
1553 break;
1554 }
1555 case 290:
1556 SpawnOffsetObj(en_scan_alien, x, y);
1557 break;
1558
1559
1560 //
1561 // Large Canister Alien - ACTIVE/WALKING
1562 //
1563 case 270:
1564 if (gamestate.difficulty < gd_hard) {
1565 break;
1566 }
1567 case 271:
1568 if (gamestate.difficulty < gd_medium) {
1569 break;
1570 }
1571 case 272:
1572 SpawnOffsetObj(en_lcan_alien, x, y);
1573 break;
1574
1575
1576 //
1577 // Gurney Mutant - AWAKE
1578 //
1579 case 275:
1580 if (gamestate.difficulty < gd_hard) {
1581 break;
1582 }
1583 case 274:
1584 if (gamestate.difficulty < gd_medium) {
1585 break;
1586 }
1587 case 273:
1588 SpawnOffsetObj(en_gurney, x, y);
1589 break;
1590
1591
1592 case 293:
1593 if (gamestate.difficulty < gd_hard) {
1594 break;
1595 }
1596 case 292:
1597 if (gamestate.difficulty < gd_medium) {
1598 break;
1599 }
1600 case 291:
1601 SpawnStand(en_liquid, x, y, 0);
1602 break;
1603
1604
1605 // P.O.D. Alien Egg
1606 //
1607 case 294:
1608 if (gamestate.difficulty < gd_hard) {
1609 scan_value = 0xff;
1610 }
1611
1612 case 276:
1613 if (gamestate.difficulty < gd_medium) {
1614 scan_value = 0xff;
1615 }
1616
1617 case 306:
1618 SpawnOffsetObj(en_podegg, x, y);
1619 if (scan_value == 0xff) {
1620 new_actor->obclass = deadobj;
1621 } else {
1622 AddTotalPoints(actor_points[podobj - rentacopobj]);
1623 AddTotalEnemy(1);
1624 }
1625 scan_value = 0xffff;
1626 break;
1627
1628 // Morphing Brown/LBlue Post -> Spider Mutant
1629 //
1630 case 610:
1631 if (!::is_ps()) {
1632 INVALID_ACTOR_ERR;
1633 }
1634
1635 if (gamestate.difficulty < gd_hard) {
1636 scan_value = 0xff;
1637 }
1638
1639 case 609:
1640 if (!::is_ps()) {
1641 INVALID_ACTOR_ERR;
1642 }
1643
1644 if (gamestate.difficulty < gd_medium) {
1645 scan_value = 0xff;
1646 }
1647
1648 case 608:
1649 if (!::is_ps()) {
1650 INVALID_ACTOR_ERR;
1651 }
1652
1653 if (scan_value == 0xff) {
1654 SpawnStatic(x, y, 402 - 315);
1655 } else {
1656 AddTotalPoints(actor_points[en_spider_mutant]);
1657 AddTotalEnemy(1);
1658 SpawnOffsetObj(en_morphing_spider_mutant, x, y);
1659 }
1660 scan_value = 0xffff;
1661 break;
1662
1663
1664 // Morphing Gray/Green Post -> Reptilian Warrior
1665 //
1666 case 592:
1667 if (!::is_ps()) {
1668 INVALID_ACTOR_ERR;
1669 }
1670
1671 if (gamestate.difficulty < gd_hard) {
1672 scan_value = 0xff;
1673 }
1674
1675 case 591:
1676 if (!::is_ps()) {
1677 INVALID_ACTOR_ERR;
1678 }
1679
1680 if (gamestate.difficulty < gd_medium) {
1681 scan_value = 0xff;
1682 }
1683
1684 case 590:
1685 if (!::is_ps()) {
1686 INVALID_ACTOR_ERR;
1687 }
1688
1689 if (scan_value == 0xff) {
1690 SpawnStatic(x, y, 403 - 315);
1691 } else {
1692 AddTotalPoints(actor_points[en_reptilian_warrior]);
1693 AddTotalEnemy(1);
1694 SpawnOffsetObj(en_morphing_reptilian_warrior, x, y);
1695 }
1696 scan_value = 0xffff;
1697 break;
1698
1699
1700
1701 // Morphing Statue -> Blue Boy
1702 //
1703 case 628:
1704 if (!::is_ps()) {
1705 INVALID_ACTOR_ERR;
1706 }
1707
1708 if (gamestate.difficulty < gd_hard) {
1709 scan_value = 0xff;
1710 }
1711
1712 case 627:
1713 if (!::is_ps()) {
1714 INVALID_ACTOR_ERR;
1715 }
1716
1717 if (gamestate.difficulty < gd_medium) {
1718 scan_value = 0xff;
1719 }
1720
1721 case 626:
1722 if (!::is_ps()) {
1723 INVALID_ACTOR_ERR;
1724 }
1725
1726 if (scan_value == 0xff) {
1727 SpawnStatic(x, y, 48 - 23);
1728 } else {
1729 AddTotalPoints(actor_points[en_mutant_human2]);
1730 AddTotalEnemy(1);
1731 SpawnOffsetObj(en_morphing_mutanthuman2, x, y);
1732 }
1733 scan_value = 0xffff;
1734 break;
1735
1736
1737 // P.O.D. Alien
1738 //
1739 case 309:
1740 if (gamestate.difficulty < gd_hard) {
1741 break;
1742 }
1743
1744 case 308:
1745 if (gamestate.difficulty < gd_medium) {
1746 break;
1747 }
1748
1749 case 307:
1750 SpawnOffsetObj(en_pod, x, y);
1751 break;
1752
1753
1754 // Electro-Sphere - Vertical Hover
1755 //
1756 case 360:
1757 if (gamestate.difficulty < gd_hard) {
1758 break;
1759 }
1760
1761 case 342:
1762 if (gamestate.difficulty < gd_medium) {
1763 break;
1764 }
1765
1766 case 324:
1767 SpawnOffsetObj(en_vertsphere, x, y);
1768 break;
1769
1770 // Electro-Sphere - Horizontal Hover
1771 //
1772 case 361:
1773 if (gamestate.difficulty < gd_hard) {
1774 break;
1775 }
1776
1777 case 343:
1778 if (gamestate.difficulty < gd_medium) {
1779 break;
1780 }
1781
1782 case 325:
1783 SpawnOffsetObj(en_horzsphere, x, y);
1784 break;
1785
1786 // Electro-Sphere - Diagonal Hover
1787 //
1788 case 362:
1789 if (gamestate.difficulty < gd_hard) {
1790 break;
1791 }
1792
1793 case 344:
1794 if (gamestate.difficulty < gd_medium) {
1795 break;
1796 }
1797
1798 case 326:
1799 SpawnOffsetObj(en_diagsphere, x, y);
1800 break;
1801
1802
1803
1804
1805 //
1806 // Stationary SWAT Guards
1807 //
1808
1809 case 252:
1810 case 253:
1811 case 254:
1812 case 255:
1813 if (gamestate.difficulty < gd_hard) {
1814 break;
1815 }
1816 tile -= 18;
1817 case 234:
1818 case 235:
1819 case 236:
1820 case 237:
1821 if (gamestate.difficulty < gd_medium) {
1822 break;
1823 }
1824 tile -= 18;
1825 case 216:
1826 case 217:
1827 case 218:
1828 case 219:
1829 SpawnStand(en_swat, x, y, tile - 216);
1830 break;
1831
1832
1833 //
1834 // Roaming SWAT Guards
1835 //
1836
1837 case 256:
1838 case 257:
1839 case 258:
1840 case 259:
1841 if (gamestate.difficulty < gd_hard) {
1842 break;
1843 }
1844 tile -= 18;
1845 case 238:
1846 case 239:
1847 case 240:
1848 case 241:
1849 if (gamestate.difficulty < gd_medium) {
1850 break;
1851 }
1852 tile -= 18;
1853 case 220:
1854 case 221:
1855 case 222:
1856 case 223:
1857 SpawnPatrol(en_swat, x, y, tile - 220);
1858 break;
1859
1860 //
1861 // STATIONARY HANGING TURRETS
1862 //
1863 case 368:
1864 case 369:
1865 case 370:
1866 case 371:
1867 if (gamestate.difficulty < gd_hard) {
1868 break;
1869 }
1870 tile -= 126;
1871 case 242:
1872 case 243:
1873 case 244:
1874 case 245:
1875 if (gamestate.difficulty < gd_medium) {
1876 break;
1877 }
1878 tile -= 18;
1879 case 224:
1880 case 225:
1881 case 226:
1882 case 227:
1883 SpawnStand(en_hang_terrot, x, y, tile - 224);
1884 new_actor->flags |= FL_STATIONARY;
1885 break;
1886
1887
1888 //
1889 // ROTATING HANGING TURRETS
1890 //
1891 case 372:
1892 case 373:
1893 case 374:
1894 case 375:
1895 if (gamestate.difficulty < gd_hard) {
1896 break;
1897 }
1898 tile -= 126;
1899 case 246:
1900 case 247:
1901 case 248:
1902 case 249:
1903 if (gamestate.difficulty < gd_medium) {
1904 break;
1905 }
1906 tile -= 18;
1907 case 228:
1908 case 229:
1909 case 230:
1910 case 231:
1911 SpawnStand(en_hang_terrot, x, y, tile - 228);
1912 break;
1913
1914
1915
1916 // --------------------------
1917 // PATH OBJECTS
1918 // --------------------------
1919 //
1920 // Swat Guards
1921 //
1922
1923 case 540:
1924 case 541:
1925 case 542:
1926 case 543:
1927 if (gamestate.difficulty < gd_hard) {
1928 break;
1929 }
1930 tile -= 18;
1931 case 522:
1932 case 523:
1933 case 524:
1934 case 525:
1935 if (gamestate.difficulty < gd_medium) {
1936 break;
1937 }
1938 tile -= 18;
1939 case 504:
1940 case 505:
1941 case 506:
1942 case 507:
1943 SpawnPatrol(en_swat, x, y, tile - 504);
1944 new_actor->flags &= ~FL_RANDOM_TURN;
1945 break;
1946
1947 //
1948 // VOLATILE MAT. TRANSPORT
1949 //
1950
1951 case 548:
1952 case 549:
1953 case 550:
1954 case 551:
1955 if (gamestate.difficulty < gd_hard) {
1956 break;
1957 }
1958 tile -= 18;
1959 case 530:
1960 case 531:
1961 case 532:
1962 case 533:
1963 if (gamestate.difficulty < gd_medium) {
1964 break;
1965 }
1966 tile -= 18;
1967 case 512:
1968 case 513:
1969 case 514:
1970 case 515:
1971 SpawnPatrol(en_volatiletransport, x, y, tile - 512);
1972 new_actor->flags &= ~FL_RANDOM_TURN;
1973 break;
1974
1975 //
1976 // FLOATING BOMB -
1977 //
1978
1979 case 544:
1980 case 545:
1981 case 546:
1982 case 547:
1983 if (gamestate.difficulty < gd_hard) {
1984 break;
1985 }
1986 tile -= 18;
1987 case 526:
1988 case 527:
1989 case 528:
1990 case 529:
1991 if (gamestate.difficulty < gd_medium) {
1992 break;
1993 }
1994 tile -= 18;
1995 case 508:
1996 case 509:
1997 case 510:
1998 case 511:
1999 SpawnPatrol(en_floatingbomb, x, y, tile - 508);
2000 new_actor->flags &= ~FL_RANDOM_TURN;
2001 break;
2002
2003 //
2004 // PRO GUARD
2005 //
2006
2007 case 594:
2008 case 595:
2009 case 596:
2010 case 597:
2011 if (gamestate.difficulty < gd_hard) {
2012 break;
2013 }
2014 tile -= 18;
2015 case 576:
2016 case 577:
2017 case 578:
2018 case 579:
2019 if (gamestate.difficulty < gd_medium) {
2020 break;
2021 }
2022 tile -= 18;
2023 case 558:
2024 case 559:
2025 case 560:
2026 case 561:
2027 SpawnPatrol(en_proguard, x, y, tile - 558);
2028 new_actor->flags &= ~FL_RANDOM_TURN;
2029 break;
2030
2031
2032 //
2033 // RENT-A-COP
2034 //
2035
2036 case 552:
2037 case 553:
2038 case 554:
2039 case 555:
2040 if (gamestate.difficulty < gd_hard) {
2041 break;
2042 }
2043 tile -= 18;
2044 case 534:
2045 case 535:
2046 case 536:
2047 case 537:
2048 if (gamestate.difficulty < gd_medium) {
2049 break;
2050 }
2051 tile -= 18;
2052 case 516:
2053 case 517:
2054 case 518:
2055 case 519:
2056 SpawnPatrol(en_rentacop, x, y, tile - 516);
2057 new_actor->flags &= ~FL_RANDOM_TURN;
2058 break;
2059
2060 // -----------------------
2061 // BOSS ACTORS
2062 // -----------------------
2063
2064 case 630: // FINAL BOSS 1
2065 case 631: // FINAL BOSS 2
2066 case 632: // FINAL BOSS 3
2067 case 633: // FINAL BOSS 4
2068 if (!::is_ps()) {
2069 INVALID_ACTOR_ERR;
2070 }
2071
2072 SpawnOffsetObj(static_cast<enemy_t>(en_final_boss1 + tile - 630), x, y);
2073 break;
2074 }
2075
2076 // If "new_actor" is an object that gives points, add those points to level total...
2077 //
2078 // "new_actor" is cleared to keep from re-adding points from the previous actor!
2079 //
2080 if (new_actor && (new_actor->obclass >= rentacopobj) && (new_actor->obclass < crate1obj)) {
2081 classtype obclass = new_actor->obclass;
2082
2083 switch (obclass) {
2084 case lcan_wait_alienobj:
2085 case scan_wait_alienobj:
2086 case gurney_waitobj:
2087 obclass++;
2088 break;
2089
2090 default:
2091 break;
2092 }
2093
2094 AddTotalPoints(actor_points[obclass - rentacopobj]);
2095 AddTotalEnemy(1);
2096 new_actor = nullptr;
2097 }
2098
2099 // Skip past FA code...
2100 //
2101 if (scan_value != 0xffff) {
2102 x++;
2103 start++;
2104 }
2105 }
2106 }
2107
2108 if (!loadedgame) {
2109 gamestuff.level[gamestate.mapon].stats.accum_inf = gamestuff.level[gamestate.mapon].stats.total_inf;
2110 }
2111 }
2112
AddTotalPoints(uint16_t points)2113 void AddTotalPoints(
2114 uint16_t points)
2115 {
2116 if (loadedgame) {
2117 return;
2118 }
2119
2120 gamestuff.level[gamestate.mapon].stats.total_points += points;
2121 }
2122
AddTotalInformants(int8_t informants)2123 void AddTotalInformants(
2124 int8_t informants)
2125 {
2126 if (loadedgame) {
2127 return;
2128 }
2129
2130 gamestuff.level[gamestate.mapon].stats.total_inf += informants;
2131 }
2132
AddTotalEnemy(uint16_t enemies)2133 void AddTotalEnemy(
2134 uint16_t enemies)
2135 {
2136 if (loadedgame) {
2137 return;
2138 }
2139
2140 gamestuff.level[gamestate.mapon].stats.total_enemy +=
2141 static_cast<uint8_t>(enemies);
2142 }
2143
SetupGameLevel()2144 void SetupGameLevel()
2145 {
2146 bool switchon = false;
2147 sci_mCacheInfo* ci = InfHintList.smInfo;
2148 int16_t x, y;
2149 uint16_t* map, tile, icon;
2150 keytype lock;
2151 uint16_t* map1, * map2;
2152 int16_t count;
2153
2154 if (!loadedgame) {
2155 gamestate.flags |= GS_CLIP_WALLS;
2156 InitGoldsternInfo();
2157 }
2158
2159 US_InitRndT(true);
2160
2161 //
2162 // load the level
2163 //
2164 CA_CacheMap(static_cast<int16_t>(
2165 gamestate.mapon + MAPS_PER_EPISODE * gamestate.episode));
2166 mapon = static_cast<int16_t>(mapon - (gamestate.episode * MAPS_PER_EPISODE));
2167
2168 mapwidth = mapheaderseg[mapon]->width;
2169 mapheight = mapheaderseg[mapon]->height;
2170
2171 if (mapwidth != 64 || mapheight != 64) {
2172 ::Quit("Map not 64 x 64.");
2173 }
2174
2175 // BBi
2176 fix_level_inplace();
2177
2178 LoadLocationText(static_cast<int16_t>(
2179 gamestate.mapon + MAPS_PER_EPISODE * gamestate.episode));
2180
2181 //
2182 // copy the wall data to a data segment array
2183 //
2184 memset(TravelTable, 0, sizeof(TravelTable));
2185 ::gamestate.initialize_local_barriers();
2186 memset(tilemap, 0, sizeof(tilemap));
2187 memset(actorat, 0, sizeof(actorat));
2188
2189 std::uninitialized_fill(
2190 ::wallheight.begin(),
2191 ::wallheight.end(),
2192 0);
2193
2194 map = mapsegs[0];
2195 map2 = mapsegs[1];
2196 for (y = 0; y < mapheight; y++) {
2197 for (x = 0; x < mapwidth; x++) {
2198 icon = *map2++;
2199 tile = *map++;
2200
2201 if (tile < AREATILE) {
2202 // solid wall
2203 tilemap[x][y] = static_cast<uint8_t>(tile);
2204
2205 switch (tile) {
2206 case RKEY_TILE:
2207 case YKEY_TILE:
2208 case BKEY_TILE:
2209 case BFG_TILE:
2210 case ION_TILE:
2211 case DETONATOR_TILE:
2212 case CLOAK_TILE:
2213 case LINC_TILE:
2214 case CLOAK_AMBUSH_TILE:
2215 if (!::is_ps()) {
2216 INVALID_ACTOR_ERR;
2217 }
2218 case AMBUSHTILE:
2219 break;
2220
2221 default:
2222 actorat[x][y] = reinterpret_cast<objtype*>(tile);
2223 break;
2224 }
2225 }
2226
2227 if ((::is_ps() && tile < 64) || icon == PUSHABLETILE) {
2228 TravelTable[x][y] |= TT_TRAVELED;
2229 }
2230 }
2231 }
2232
2233 //
2234 // spawn doors
2235 //
2236 InitActorList(); // start spawning things with a clean slate
2237 InitDoorList();
2238
2239 InitMsgCache((mCacheList*)&ConHintList, sizeof(ConHintList), sizeof(ConHintList.cmInfo[0]));
2240 InitMsgCache((mCacheList*)&InfHintList, sizeof(InfHintList), sizeof(InfHintList.smInfo[0]));
2241 InitMsgCache((mCacheList*)&NiceSciList, sizeof(NiceSciList), sizeof(InfHintList.smInfo[0]));
2242 InitMsgCache((mCacheList*)&MeanSciList, sizeof(MeanSciList), sizeof(InfHintList.smInfo[0]));
2243
2244 InitStaticList();
2245
2246 map = mapsegs[0];
2247 map1 = mapsegs[1];
2248
2249 NumEAWalls = 0;
2250 alerted = 0;
2251 LastInfoAttacker = nothing;
2252
2253 // BBi
2254 bool is_red_key_present = false;
2255 bool is_projection_generator_present = false;
2256 // BBi
2257
2258 for (y = 0; y < mapheight; y++) {
2259 for (x = 0; x < mapwidth; x++) {
2260 tile = *map++;
2261 lock = static_cast<keytype>(*map1);
2262
2263 if (y < 63 && x < 63 && *map == 30) {
2264 gamestate.wintilex = x + 1;
2265 gamestate.wintiley = y;
2266 }
2267
2268 if (tile >= 88 && tile <= 105) {
2269 //
2270 // KEYS
2271 //
2272
2273 switch (static_cast<int>(lock)) {
2274 case 55:
2275 case 56:
2276 lock = static_cast<keytype>(kt_red + lock - 55);
2277 *map1 = 0;
2278 break;
2279
2280 case 58:
2281 lock = kt_blue;
2282 *map1 = 0;
2283 break;
2284
2285 case 57:
2286 if (::is_ps()) {
2287 lock = kt_none;
2288 break;
2289 }
2290
2291 lock = kt_green;
2292 *map1 = 0;
2293 break;
2294
2295 case 59:
2296 if (::is_ps()) {
2297 lock = kt_none;
2298 break;
2299 }
2300
2301 lock = kt_gold;
2302 *map1 = 0;
2303 break;
2304
2305 default:
2306 lock = kt_none;
2307 }
2308
2309 //
2310 // DOOR
2311 //
2312
2313 switch (tile) {
2314
2315 case 88:
2316 case 89:
2317 SpawnDoor(x, y, !(tile % 2), lock, dr_bio);
2318 break;
2319
2320 case 90:
2321 case 91:
2322 SpawnDoor(x, y, !(tile % 2), lock, dr_normal);
2323 break;
2324
2325 case 92:
2326 case 93:
2327 SpawnDoor(x, y, !(tile % 2), lock, dr_prison);
2328 break;
2329
2330 case 94:
2331 case 95:
2332 SpawnDoor(x, y, !(tile % 2), lock, dr_elevator);
2333 break;
2334
2335 case 96:
2336 case 97:
2337 SpawnDoor(x, y, !(tile % 2), lock, dr_high_security);
2338 break;
2339
2340 case 98: // oneway left - Vert
2341 case 99: // oneway up - Horz
2342 case 100: // oneway right - Vert
2343 case 101: // oneway down - Horz
2344 SpawnDoor(x, y, !(tile % 2), lock, static_cast<door_t>(dr_oneway_left + (tile - 98)));
2345 break;
2346
2347 case 102:
2348 case 103:
2349 SpawnDoor(x, y, !(tile % 2), lock, dr_office);
2350 break;
2351
2352 case 104:
2353 case 105:
2354 SpawnDoor(x, y, !(tile % 2), lock, dr_space);
2355 break;
2356
2357
2358 }
2359 } else {
2360 switch (tile) {
2361 case SODATILE:
2362 if (!loadedgame) {
2363 SpawnConcession(x, y, static_cast<uint16_t>(lock), CT_BEVS);
2364 *map1 = 0;
2365 }
2366 break;
2367
2368
2369
2370 case FOODTILE:
2371 if (!loadedgame) {
2372 SpawnConcession(x, y, static_cast<uint16_t>(lock), CT_FOOD);
2373 *map1 = 0;
2374 }
2375 break;
2376
2377 case EATILE:
2378 eaList[static_cast<int>(NumEAWalls)].tilex = static_cast<int8_t>(x);
2379 eaList[static_cast<int>(NumEAWalls)].tiley = static_cast<int8_t>(y);
2380 eaList[static_cast<int>(NumEAWalls)].aliens_out = 0;
2381 if ((lock & 0xff00) == 0xfa00) {
2382 eaList[static_cast<int>(NumEAWalls)].delay = 60 * (lock & 0xff);
2383 } else {
2384 eaList[static_cast<int>(NumEAWalls)].delay = 60 * 8 + Random(60 * 22);
2385 }
2386 if (NumEAWalls++ == MAXEAWALLS) {
2387 ::Quit("Too many Electro-Alien walls in level.");
2388 }
2389 break;
2390
2391 case ON_SWITCH:
2392 switchon = true;
2393 case OFF_SWITCH: {
2394 if (::is_aog()) {
2395 if (map1[1] != 0) {
2396 uint8_t level = 0xFF;
2397
2398 if (map1[0] != 0xF8FF) {
2399 level = static_cast<uint8_t>(map1[0] & 0xFF);
2400 }
2401
2402 if (level == ::gamestate.mapon) {
2403 level = 0xFF;
2404 }
2405
2406 auto switch_x = static_cast<uint8_t>((map1[1] / 256) & 0xFF);
2407 auto switch_y = static_cast<uint8_t>(map1[1] & 0xFF);
2408
2409 map1[1] = 0;
2410 map1[0] = 0xF800 | UpdateBarrierTable(level, switch_x, switch_y, switchon);
2411
2412 if (level != 0xFF) {
2413 ::store_cross_barrier(level, switch_x, switch_y, switchon);
2414 }
2415 }
2416 } else {
2417 auto switch_x = static_cast<uint8_t>((map1[0] / 256) & 0xFF);
2418 auto switch_y = static_cast<uint8_t>(map1[0] & 0xFF);
2419
2420 map1[0] = 0xF800 | UpdateBarrierTable(0xFF, switch_x, switch_y, switchon);
2421 }
2422
2423 // Init for next time.
2424
2425 switchon = false;
2426 break;
2427 }
2428 }
2429 }
2430
2431 // BBi
2432 if (map1[0] == 55) {
2433 is_red_key_present = true;
2434 }
2435
2436 if (map1[0] == 177) {
2437 is_projection_generator_present = true;
2438 }
2439
2440 switch (map1[0]) {
2441 case 445:
2442 case 463:
2443 case 481:
2444 is_red_key_present = true;
2445 break;
2446
2447 default:
2448 break;
2449 }
2450 // BBi
2451
2452 map1++;
2453 }
2454 }
2455
2456
2457 // BBi
2458 ::apply_cross_barriers();
2459 // BBi
2460
2461 //
2462 // spawn actors
2463 //
2464
2465 ScanInfoPlane();
2466 ConnectBarriers();
2467
2468 // Init informant stuff
2469 //
2470 count = InfHintList.NumMsgs;
2471 LastInfArea = 0xff;
2472 FirstGenInfMsg = 0;
2473 for (; (ci->areanumber != 0xff) && (count--); ci++) {
2474 FirstGenInfMsg++;
2475 }
2476 TotalGenInfMsgs = InfHintList.NumMsgs - FirstGenInfMsg;
2477
2478
2479 //
2480 // Take out the special tiles that were not used...
2481 //
2482
2483 map = mapsegs[0];
2484 for (y = 0; y < mapheight; y++) {
2485 for (x = 0; x < mapwidth; x++) {
2486 tile = *map++;
2487 switch (tile) {
2488 case RKEY_TILE:
2489 case YKEY_TILE:
2490 case BKEY_TILE:
2491 case BFG_TILE:
2492 case ION_TILE:
2493 case DETONATOR_TILE:
2494 case CLOAK_TILE:
2495 case LINC_TILE:
2496 case CLOAK_AMBUSH_TILE:
2497 if (!::is_ps()) {
2498 break;
2499 }
2500 case AMBUSHTILE:
2501 tilemap[x][y] = 0;
2502 if (actorat[x][y] == (objtype*)AMBUSHTILE) {
2503 actorat[x][y] = nullptr;
2504 }
2505 *(map - 1) = GetAreaNumber(static_cast<int8_t>(x), static_cast<int8_t>(y));
2506 break;
2507 }
2508 }
2509 }
2510
2511 //
2512 // have the caching manager load and purge stuff to make sure all marks
2513 // are in memory
2514 //
2515 CA_LoadAllSounds();
2516
2517 if (::is_aog()) {
2518 if (!is_red_key_present &&
2519 gamestate.mapon > 0 &&
2520 gamestate.mapon < 10 &&
2521 gamestuff.level[gamestate.mapon + 1].locked)
2522 {
2523 ::Quit("No red key on floor {}.", gamestate.mapon);
2524 }
2525
2526 if (::is_aog_full() &&
2527 gamestate.episode == 5 &&
2528 gamestate.mapon == 9 &&
2529 !is_projection_generator_present)
2530 {
2531 ::Quit("No projection generator(s) on floor 10 episode 6.");
2532 }
2533 } else {
2534 //
2535 // Check and make sure a detonator is in a 'locked' level.
2536 //
2537
2538 if (gamestate.mapon < 20 &&
2539 !detonators_spawned &&
2540 gamestuff.level[gamestate.mapon + 1].locked)
2541 {
2542 ::Quit("No Fision/Plasma Detonator in level!");
2543 }
2544 }
2545 }
2546
2547
2548 // ------------------------------------------------------------------------
2549 // LoadLocationText()
2550 // ------------------------------------------------------------------------
LoadLocationText(int16_t textNum)2551 void LoadLocationText(
2552 int16_t textNum)
2553 {
2554 char* temp;
2555
2556 LoadMsg(LocationText, LEVEL_DESCS, textNum + 1, MAX_LOCATION_DESC_LEN);
2557 temp = strstr(LocationText, "^XX");
2558 if (temp) {
2559 *temp = 0;
2560 }
2561 }
2562
DrawPlayBorder()2563 void DrawPlayBorder()
2564 {
2565 ::vid_set_ui_mask_3d(
2566 ::playstate == ex_transported);
2567
2568 ::VL_Bar(
2569 0,
2570 ::ref_view_top,
2571 ::vga_ref_width,
2572 ::ref_3d_margin,
2573 BLACK);
2574
2575 ::VL_Bar(
2576 0,
2577 ::ref_3d_view_bottom,
2578 ::vga_ref_width,
2579 ::ref_3d_margin,
2580 BLACK);
2581 }
2582
2583 // --------------------------------------------------------------------------
2584 // BMAmsg() - These messages are displayed by the Text Presenter!
2585 // --------------------------------------------------------------------------
BMAmsg(const char * msg)2586 void BMAmsg(
2587 const char* msg)
2588 {
2589 const int16_t BMAx1 = 0; // outer bevel
2590 const int16_t BMAy1 = 152;
2591 const int16_t BMAw1 = 320;
2592 const int16_t BMAh1 = 48;
2593
2594 const int16_t BMAx2 = BMAx1 + 7; // inner bevel
2595 const int16_t BMAy2 = BMAy1 + 4;
2596 const int16_t BMAw2 = BMAw1 - 14;
2597 const int16_t BMAh2 = BMAh1 - 8;
2598
2599 BevelBox(BMAx1, BMAy1, BMAw1, BMAh1, BORDER_HI_COLOR, BORDER_MED_COLOR, BORDER_LO_COLOR);
2600 BevelBox(BMAx2, BMAy2, BMAw2, BMAh2, BORDER_LO_COLOR, BORDER_MED_COLOR, BORDER_HI_COLOR);
2601
2602 if (msg) {
2603 PresenterInfo pi;
2604 fontstruct* font = (fontstruct*)grsegs[STARTFONT + fontnumber];
2605 int8_t numlines = 1;
2606 const char* p = msg;
2607 int16_t cheight;
2608
2609 memset(&pi, 0, sizeof(pi));
2610 pi.flags = TPF_CACHE_NO_GFX;
2611 pi.script[0] = p;
2612 while (*p) {
2613 if (*p++ == TP_RETURN_CHAR) {
2614 numlines++;
2615 }
2616 }
2617 cheight = font->height * numlines + 1 + (TP_MARGIN * 2);
2618
2619 pi.xl = BMAx2 + 1;
2620 pi.yl = BMAy2 + (BMAh2 - cheight) / 2;
2621 pi.xh = pi.xl + BMAw2 - 3;
2622 pi.yh = pi.yl + cheight - 1;
2623 pi.bgcolor = BORDER_MED_COLOR;
2624 pi.ltcolor = BORDER_HI_COLOR;
2625 fontcolor = BORDER_TEXT_COLOR;
2626 pi.shcolor = pi.dkcolor = BORDER_LO_COLOR;
2627 pi.fontnumber = static_cast<int8_t>(fontnumber);
2628 TP_InitScript(&pi);
2629 TP_Presenter(&pi);
2630 }
2631 }
2632
2633 // ----------------------------------------------------------------------
2634 // CacheBMAmsg() - Caches in a Message Number and displays it using
2635 // BMAmsg()
2636 // ----------------------------------------------------------------------
CacheBMAmsg(uint16_t MsgNum)2637 void CacheBMAmsg(
2638 uint16_t MsgNum)
2639 {
2640 char* string, * pos;
2641
2642 CA_CacheGrChunk(MsgNum);
2643 string = (char*)grsegs[MsgNum];
2644
2645 pos = strstr(string, "^XX");
2646 *(pos + 3) = 0;
2647
2648 BMAmsg(string);
2649
2650 UNCACHEGRCHUNK(MsgNum);
2651 }
2652
BevelBox(int16_t xl,int16_t yl,int16_t w,int16_t h,uint8_t hi,uint8_t med,uint8_t lo)2653 void BevelBox(
2654 int16_t xl,
2655 int16_t yl,
2656 int16_t w,
2657 int16_t h,
2658 uint8_t hi,
2659 uint8_t med,
2660 uint8_t lo)
2661 {
2662 int16_t xh = xl + w - 1, yh = yl + h - 1;
2663 uint8_t hc;
2664
2665 VWB_Bar(xl, yl, w, h, med); // inside
2666
2667 hc = med + 1;
2668
2669 VWB_Hlin(xl, xh, yl, hi); // top
2670 VWB_Hlin(xl, xh, yh, lo); // bottom
2671 VWB_Vlin(yl, yh, xl, hi); // left
2672 VWB_Vlin(yl, yh, xh, lo); // right
2673 VWB_Plot(xl, yh, hc); // lower-left
2674 VWB_Plot(xh, yl, hc); // upper-right
2675 }
2676
ShadowPrintLocationText(sp_type type)2677 void ShadowPrintLocationText(
2678 sp_type type)
2679 {
2680 const char* DebugText = "-- DEBUG MODE ENABLED --";
2681 const char* s = nullptr, * ls_text[3] = { "-- LOADING --", "-- SAVING --", "-- CHANGE VIEW SIZE --" };
2682 int w, h;
2683
2684 // Used for all fields...
2685 //
2686 py = 5;
2687 fontcolor = 0xaf;
2688
2689 // Print LOCATION info...
2690 //
2691 switch (type) {
2692 case sp_normal:
2693 // Print LEVEL info...
2694 //
2695
2696 if (::is_aog()) {
2697 ::px = 17;
2698 } else {
2699 ::px = 13;
2700 }
2701
2702 if ((!::is_ps() && (gamestate.mapon % 10) == 0) ||
2703 (::is_ps() && gamestate.mapon > 19))
2704 {
2705 ::ShPrint(" SECRET ", 0, false);
2706 } else {
2707 if (!::is_ps()) {
2708 ShPrint("FLOOR: ", 0, false);
2709 } else {
2710 ShPrint(" AREA: ", 0, false);
2711 }
2712 if (!type) {
2713 auto map_string = std::to_string(
2714 ::is_aog() ? gamestate.mapon : gamestate.mapon + 1);
2715
2716 ::ShPrint(map_string.c_str(), 0, false);
2717 }
2718 }
2719
2720 // Print LIVES info...
2721 //
2722 px = 267;
2723 ShPrint("LIVES: ", 0, false);
2724 if (!type) {
2725 auto lives_string = std::to_string(gamestate.lives);
2726 ::ShPrint(lives_string.c_str(), 0, false);
2727 }
2728
2729 // Print location text
2730 //
2731
2732 if (DebugOk || (gamestate.flags & (GS_QUICKRUN | GS_STARTLEVEL | GS_TICS_FOR_SCORE | GS_MUSIC_TEST | GS_SHOW_OVERHEAD)))
2733 {
2734 s = DebugText;
2735 } else {
2736 s = LocationText;
2737 }
2738 break;
2739
2740 case sp_changeview:
2741 case sp_loading:
2742 case sp_saving:
2743 s = ls_text[type - sp_loading];
2744 break;
2745
2746 default:
2747 break;
2748 }
2749
2750 VW_MeasurePropString(s, &w, &h);
2751 px = static_cast<int16_t>(160 - w / 2);
2752 ShPrint(s, 0, false);
2753 }
2754
DrawTopInfo(sp_type type)2755 void DrawTopInfo(
2756 sp_type type)
2757 {
2758 auto old = static_cast<int8_t>(fontnumber);
2759
2760 LatchDrawPic(0, 0, TOP_STATUSBARPIC);
2761 fontnumber = 2;
2762 ShadowPrintLocationText(type);
2763 fontnumber = old;
2764 }
2765
DrawPlayScreen(bool InitInfoMsg)2766 void DrawPlayScreen(
2767 bool InitInfoMsg)
2768 {
2769 if (::loadedgame) {
2770 return;
2771 }
2772
2773 if (::playstate != ex_transported) {
2774 VW_FadeOut();
2775 }
2776
2777 ::WindowW = 253;
2778 ::WindowH = 8;
2779 ::fontnumber = 2;
2780
2781 ::DrawPlayBorder();
2782
2783 ::LatchDrawPic(0, 200 - STATUSLINES, STATUSBARPIC);
2784 ::LatchDrawPic(0, 0, TOP_STATUSBARPIC);
2785
2786 ::ShadowPrintLocationText(sp_normal);
2787
2788 ::DrawHealth();
2789 ::DrawKeys();
2790 ::DrawWeapon();
2791 ::DrawScore();
2792
2793 ::InitInfoArea();
2794
2795 if (InitInfoMsg) {
2796 DISPLAY_MSG("R.E.B.A.\rAGENT: BLAKE STONE\rALL SYSTEMS READY.", MP_max_val, MT_NOTHING);
2797 } else {
2798 ::DisplayNoMoMsgs();
2799 }
2800
2801 ::ForceUpdateStatusBar();
2802 }
2803
DrawWarpIn()2804 void DrawWarpIn()
2805 {
2806 ::InitInfoArea();
2807
2808 ::DisplayInfoMsg(
2809 "\r\r TRANSPORTING...",
2810 MP_POWERUP,
2811 2 * 60,
2812 MT_GENERAL);
2813
2814 ::DrawHealth();
2815 ::DrawKeys();
2816 ::DrawWeapon();
2817 ::DrawScore();
2818 ::WindowW = 253;
2819 ::WindowH = 8;
2820 ::fontnumber = 2;
2821
2822 VW_Bar(
2823 0,
2824 ::ref_view_top,
2825 ::vga_ref_width,
2826 ::ref_view_height,
2827 BLACK);
2828
2829 ::LatchDrawPic(0, ::ref_view_bottom, ::STATUSBARPIC);
2830 ::LatchDrawPic(0, 0, ::TOP_STATUSBARPIC);
2831
2832 ::ShadowPrintLocationText(sp_normal);
2833 ::UpdateStatusBar();
2834
2835 ::sd_play_player_sound(::WARPINSND, bstone::AC_ITEM);
2836
2837 ::fizzlein = true;
2838
2839 ::ThreeDRefresh();
2840 }
2841
Warped()2842 void Warped()
2843 {
2844 int16_t iangle;
2845
2846 DisplayInfoMsg("\r\r\r TRANSPORTING OUT", MP_POWERUP, 7 * 60, MT_GENERAL);
2847 gamestate.old_weapons[3] = gamestate.weapon;
2848 gamestate.weapon = -1; // take away weapon
2849
2850 ThreeDRefresh();
2851
2852 if (screenfaded) {
2853 VW_FadeIn();
2854 }
2855
2856 iangle = (((player->dir + 4) % 8) >> 1) * 90;
2857
2858 RotateView(iangle, 2);
2859
2860 gamestate.weapon = gamestate.old_weapons[3];
2861 gamestate.attackframe = gamestate.attackcount = gamestate.weaponframe = 0;
2862
2863 IN_ClearKeysDown();
2864
2865 ::sd_play_player_sound(WARPINSND, bstone::AC_ITEM);
2866
2867 bstone::GenericFizzleFX fizzle(
2868 BLACK,
2869 true);
2870
2871 fizzle.initialize();
2872
2873 static_cast<void>(fizzle.present());
2874
2875 IN_UserInput(100);
2876 SD_WaitSoundDone();
2877 }
2878
2879
Died()2880 void Died()
2881 {
2882 const uint8_t DEATHROTATE = 2;
2883
2884 int16_t iangle;
2885
2886 gamestate.weapon = -1; // take away weapon
2887
2888 ::sd_play_player_sound(PLAYERDEATHSND, bstone::AC_VOICE);
2889
2890 iangle = CalcAngle(player, killerobj);
2891
2892 RotateView(iangle, DEATHROTATE);
2893
2894 //
2895 // fade to red
2896 //
2897 FinishPaletteShifts();
2898
2899 bstone::GenericFizzleFX fizzle_fx(
2900 0x17,
2901 true);
2902
2903 fizzle_fx.initialize();
2904
2905 static_cast<void>(fizzle_fx.present());
2906
2907 IN_UserInput(100);
2908
2909 SD_WaitSoundDone();
2910 StopMusic();
2911
2912 gamestate.lives--;
2913
2914 if (gamestate.lives > -1) {
2915 gamestate.health = 100;
2916 gamestate.weapons = 1 << wp_autocharge; // |1<<wp_plasma_detonators;
2917 gamestate.weapon = gamestate.chosenweapon = wp_autocharge;
2918
2919 gamestate.ammo = STARTAMMO;
2920 gamestate.attackframe = gamestate.attackcount =
2921 gamestate.weaponframe = 0;
2922
2923 gamestate.flags |= (GS_CLIP_WALLS | GS_ATTACK_INFOAREA);
2924
2925 DrawHealth();
2926 DrawKeys();
2927 DrawWeapon();
2928 DrawScore();
2929 DrawKeys();
2930 ForceUpdateStatusBar();
2931 }
2932 }
2933
2934 // --------------------------------------------------------------------------
2935 // LoseScreen() - Displays the Goldstern/DamagedReba message...
2936 // --------------------------------------------------------------------------
LoseScreen()2937 void LoseScreen()
2938 {
2939 PresenterInfo pi;
2940
2941 VW_FadeOut();
2942
2943 memset(&pi, 0, sizeof(pi));
2944 pi.flags = TPF_USE_CURRENT | TPF_SHOW_CURSOR | TPF_SCROLL_REGION | TPF_CONTINUE | TPF_TERM_SOUND | TPF_ABORTABLE;
2945 pi.xl = 14;
2946 pi.yl = 141;
2947 pi.xh = 14 + 293;
2948 pi.yh = 141 + 32;
2949 pi.ltcolor = 15;
2950 pi.bgcolor = 0;
2951 pi.dkcolor = 1;
2952 pi.shcolor = 1;
2953 pi.fontnumber = 2;
2954 pi.cur_x = static_cast<uint16_t>(-1);
2955 pi.print_delay = 2;
2956
2957 ClearMemory();
2958 StopMusic();
2959
2960 CA_CacheScreen(LOSEPIC);
2961 VW_UpdateScreen();
2962
2963 TP_LoadScript(nullptr, &pi, LOSETEXT);
2964
2965 // Now Presenting... The Loser Prize.. I nice message directly from Dr.
2966 // ============== Goldstern himself! Oooo Ohhhhh <clap> <clap> ...
2967 //
2968
2969 VW_FadeIn();
2970 TP_Presenter(&pi);
2971 VW_FadeOut();
2972
2973 TP_FreeScript(&pi, LOSETEXT);
2974
2975 screenfaded = true;
2976
2977 IN_ClearKeysDown();
2978 }
2979
2980 // --------------------------------------------------------------------------
2981 // RotateView()
2982 //
2983 // PARAMETERS:
2984 // DestAngle - Destination angle to rotate player->angle to.
2985 // RotSpeed - Rotation Speed
2986 // --------------------------------------------------------------------------
RotateView(int16_t DestAngle,uint8_t RotSpeed)2987 void RotateView(
2988 int16_t DestAngle,
2989 uint8_t RotSpeed)
2990 {
2991 int16_t curangle, clockwise, counter, change;
2992 objtype* obj;
2993 bool old_godmode = godmode;
2994
2995 if (player->angle > DestAngle) {
2996 counter = player->angle - DestAngle;
2997 clockwise = ANGLES - player->angle + DestAngle;
2998 } else {
2999 clockwise = DestAngle - player->angle;
3000 counter = player->angle + ANGLES - DestAngle;
3001 }
3002
3003 godmode = true;
3004 curangle = player->angle;
3005
3006 controly = 0;
3007 if (clockwise < counter) {
3008 //
3009 // rotate clockwise
3010 //
3011 if (curangle > DestAngle) {
3012 curangle -= ANGLES;
3013 }
3014 controlx = -1;
3015 do {
3016 change = tics * RotSpeed;
3017 if (curangle + change > DestAngle) {
3018 change = DestAngle - curangle;
3019 }
3020
3021 curangle += change;
3022 player->angle += change;
3023 if (player->angle >= ANGLES) {
3024 player->angle -= ANGLES;
3025 }
3026
3027 for (obj = player->next; obj; obj = obj->next) {
3028 DoActor(obj);
3029 }
3030 ThreeDRefresh();
3031 CalcTics();
3032 } while (curangle != DestAngle);
3033 } else {
3034 //
3035 // rotate counterclockwise
3036 //
3037 if (curangle < DestAngle) {
3038 curangle += ANGLES;
3039 }
3040 controlx = 1;
3041 do {
3042 change = -tics * RotSpeed;
3043 if (curangle + change < DestAngle) {
3044 change = DestAngle - curangle;
3045 }
3046
3047 curangle += change;
3048 player->angle += change;
3049 if (player->angle < 0) {
3050 player->angle += ANGLES;
3051 }
3052
3053 for (obj = player->next; obj; obj = obj->next) {
3054 DoActor(obj);
3055 }
3056 ThreeDRefresh();
3057 CalcTics();
3058 } while (curangle != DestAngle);
3059 }
3060
3061 controlx = 0;
3062 player->dir = static_cast<dirtype>(((player->angle + 22) % 360) / 45);
3063 godmode = old_godmode;
3064
3065 }
3066
GameLoop()3067 void GameLoop()
3068 {
3069 // BBi
3070 ::vid_is_hud = true;
3071 ::vid_set_ui_mask_3d(false);
3072 // BBi
3073
3074 bool quit = false;
3075
3076 extern bool sqActive;
3077
3078 char Score[13];
3079 bool died;
3080
3081 restartgame:
3082
3083 ClearMemory();
3084 SETFONTCOLOR(0, 15);
3085 DrawPlayScreen(true);
3086
3087 died = false;
3088 do {
3089 extern int16_t pickquick;
3090
3091 ingame = true;
3092
3093 if (died && pickquick) {
3094 char string[] = " Auto Quick Load? ";
3095
3096 WindowX = WindowY = 0;
3097 WindowW = 320;
3098 WindowH = 152;
3099
3100 if (Confirm(string)) {
3101 playstate = ex_stillplaying;
3102 DrawPlayBorder();
3103 VW_UpdateScreen();
3104 US_ControlPanel(ScanCode::sc_f9);
3105 }
3106
3107 DrawPlayBorder();
3108 VW_UpdateScreen();
3109 }
3110
3111 if (!sqActive) {
3112 StartMusic(false);
3113 }
3114
3115 if (!(loadedgame || LevelInPlaytemp(gamestate.mapon))) {
3116 ::gamestate.score = ::gamestate.oldscore;
3117 ::gamestate.tic_score = ::gamestate.oldscore;
3118 ::memcpy(::gamestate.numkeys, ::gamestate.old_numkeys, sizeof(::gamestate.numkeys));
3119 ::gamestate.restore_local_barriers();
3120 ::gamestate.rpower = ::gamestate.old_rpower;
3121 ::gamestate.tokens = ::gamestate.old_tokens;
3122 ::gamestate.weapons = ::gamestate.old_weapons[0];
3123 ::gamestate.weapon = ::gamestate.old_weapons[1];
3124 ::gamestate.chosenweapon = ::gamestate.old_weapons[2];
3125 ::gamestate.ammo = ::gamestate.old_ammo;
3126 ::gamestate.plasma_detonators = ::gamestate.old_plasma_detonators;
3127 ::gamestate.boss_key_dropped = ::gamestate.old_boss_key_dropped;
3128 ::gamestuff.level = ::gamestuff.old_levelinfo;
3129 ::DrawKeys();
3130 ::DrawScore();
3131 }
3132
3133 startgame = false;
3134 if (!loadedgame) {
3135 if (LS_current == -1) {
3136 // BBi
3137 ::vid_clear_3d();
3138 // BBi
3139
3140 DrawTopInfo(sp_loading);
3141 DisplayPrepingMsg(prep_msg);
3142 LS_current = 1;
3143 LS_total = 20;
3144 }
3145 LoadLevel(gamestate.mapon);
3146 }
3147
3148 LS_current = LS_total = -1;
3149
3150 SetPlaneViewSize();
3151 if (loadedgame) {
3152 loadedgame = false;
3153 }
3154
3155 if (died) {
3156 WindowY = 188;
3157 PreloadUpdate(1, 1);
3158 died = false;
3159 DrawPlayScreen(true);
3160 } else {
3161 PreloadGraphics();
3162 if (playstate == ex_transported) {
3163 DrawWarpIn();
3164 } else {
3165 DrawPlayScreen(false);
3166 }
3167 }
3168
3169 if (!sqActive) {
3170 StartMusic(false);
3171 }
3172
3173 PlayLoop();
3174 LS_current = LS_total = -1;
3175 died = false;
3176
3177 StopMusic();
3178 ingame = false;
3179
3180 if (startgame || loadedgame) {
3181 goto restartgame;
3182 }
3183
3184 switch (playstate) {
3185
3186 case ex_transported: // Same as ex_completed
3187 Warped();
3188
3189 case ex_completed:
3190 case ex_secretlevel:
3191 case ex_warped:
3192 ClearMemory();
3193 gamestate.mapon++;
3194 ClearNClose();
3195 DrawTopInfo(sp_loading);
3196 DisplayPrepingMsg(prep_msg);
3197 WindowY = 181;
3198 LS_current = 1;
3199 LS_total = 38;
3200 StartMusic(false);
3201 SaveLevel(gamestate.lastmapon);
3202
3203 gamestate.old_rpower = gamestate.rpower;
3204 gamestate.oldscore = gamestate.score;
3205 memcpy(gamestate.old_numkeys, gamestate.numkeys, sizeof(gamestate.old_numkeys));
3206 gamestate.old_tokens = gamestate.tokens;
3207 ::gamestate.store_local_barriers();
3208 gamestate.old_weapons[0] = gamestate.weapons;
3209 gamestate.old_weapons[1] = gamestate.weapon;
3210 gamestate.old_weapons[2] = gamestate.chosenweapon;
3211 gamestate.old_ammo = gamestate.ammo;
3212 gamestate.old_boss_key_dropped = gamestate.boss_key_dropped;
3213 gamestuff.old_levelinfo = gamestuff.level;
3214 break;
3215
3216 case ex_died:
3217 if (InstantQuit) {
3218 InstantQuit = false;
3219 } else {
3220 Died();
3221
3222 died = true; // don't "get psyched!"
3223
3224 if (gamestate.lives > -1) {
3225 ClearMemory();
3226 break; // more lives left
3227 }
3228
3229 LoseScreen();
3230 }
3231
3232
3233 case ex_victorious:
3234 MainMenu[MM_SAVE_MISSION].active = AT_DISABLED;
3235 MainMenu[MM_VIEW_SCORES].routine = &CP_ViewScores;
3236 strcpy(MainMenu[MM_VIEW_SCORES].string, "HIGH SCORES");
3237
3238 if (playstate == ex_victorious) {
3239 ThreeDRefresh();
3240 ThreeDRefresh();
3241 }
3242
3243 ClearMemory();
3244
3245 if (playstate == ex_victorious) {
3246 fontnumber = 1;
3247 CA_CacheGrChunk(STARTFONT + 1);
3248 memset(update, 0, sizeof(update));
3249 CacheBMAmsg(YOUWIN_TEXT);
3250
3251 // BBi
3252 #if 0
3253 VW_ScreenToScreen(PAGE1START, ::bufferofs, 320, 200);
3254 #endif
3255
3256 UNCACHEGRCHUNK(STARTFONT + 1);
3257
3258 ::sd_play_player_sound(BONUS1SND, bstone::AC_ITEM);
3259
3260 SD_WaitSoundDone();
3261 IN_UserInput(5 * 60);
3262 ClearMemory();
3263 }
3264
3265 VW_FadeOut();
3266
3267 sprintf(Score, "%d", gamestate.score);
3268 piStringTable[0] = Score;
3269
3270 if (playstate == ex_victorious) {
3271 if (!::is_ps()) {
3272 movie_t movie = mv_intro;
3273
3274 switch (gamestate.episode) {
3275 case 0:
3276 case 1:
3277 case 3:
3278 movie = mv_final2;
3279 break;
3280
3281 case 2:
3282 case 4:
3283 movie = mv_final3;
3284 break;
3285
3286 case 5:
3287 movie = mv_final;
3288 break;
3289 }
3290
3291 ::DoMovie(movie, nullptr);
3292 } else {
3293 CA_CacheGrChunk(ENDINGPALETTE);
3294
3295 DoMovie(mv_final, grsegs[ENDINGPALETTE]);
3296
3297 UNCACHEGRCHUNK(ENDINGPALETTE);
3298 }
3299
3300 NewViewSize(); // Recreates & Allocs the ScaleDirectory
3301 Breifing(BT_WIN, gamestate.episode);
3302 }
3303
3304 CheckHighScore(gamestate.score, gamestate.mapon + 1);
3305
3306 return;
3307
3308 case ex_abort:
3309 quit = true;
3310 break;
3311
3312 default:
3313 ClearMemory();
3314 break;
3315 }
3316 } while (!quit);
3317
3318
3319 // BBi
3320 ::vid_is_hud = false;
3321 // BBi
3322 }
3323
3324 // BBi
is_map_sha1_match(const std::vector<std::string> & sha1s)3325 static bool is_map_sha1_match(
3326 const std::vector<std::string>& sha1s)
3327 {
3328 return std::any_of(
3329 sha1s.cbegin(),
3330 sha1s.cend(),
3331 [] (const std::string& sha1_string)
3332 {
3333 return ::map_sha1_string == sha1_string;
3334 }
3335 );
3336 }
3337
is_map_compressed_size_match(const std::vector<int> & sizes)3338 static bool is_map_compressed_size_match(
3339 const std::vector<int>& sizes)
3340 {
3341 return std::any_of(
3342 sizes.cbegin(),
3343 sizes.cend(),
3344 [] (int size)
3345 {
3346 return ::map_compressed_size == size;
3347 }
3348 );
3349 }
3350
fix_level_inplace()3351 static void fix_level_inplace()
3352 {
3353 static const std::vector<int> e2m6_sizes = {
3354 // v1.0
3355 6412,
3356
3357 // v2.0-v3.0
3358 6414,
3359 };
3360
3361 static const std::vector<std::string> e2m6_sha1s = {
3362 // v1.0
3363 "b0444bf3de386e4cac7654b996bf5341090cc1b5",
3364
3365 // v2.0-v3.0
3366 "e60d5674dcfb5f450e3a936885e361fe61cb7725",
3367 };
3368
3369
3370 // Fix standing bio-tech near volatile containers
3371 // (E2M6; x: 38; y: 26)
3372 // (E2M6; x: 55; y: 33)
3373 //
3374 if (::is_aog_full() &&
3375 !::loadedgame &&
3376 ::gamestate.episode == 1 &&
3377 ::gamestate.mapon == 6 &&
3378 ::is_map_compressed_size_match(e2m6_sizes) &&
3379 ::is_map_sha1_match(e2m6_sha1s))
3380 {
3381 // Replace standing bio-tech with a moving one.
3382 ::mapsegs[1][(26 * MAPSIZE) + 38] = 157;
3383 ::mapsegs[1][(33 * MAPSIZE) + 55] = 157;
3384 }
3385 }
3386