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
27
28 // ===========================================================================
29 //
30 // PROTOTYPES
31 //
32 // ===========================================================================
33
34 void OpenDoor(
35 int16_t door);
36
37 void CloseDoor(
38 int16_t door);
39
40 void PlaceItemNearTile(
41 int16_t itemtype,
42 int16_t tilex,
43 int16_t tiley);
44
45 void HealSelf(
46 int16_t points);
47
48
49 // ===========================================================================
50 //
51 // LOCALS
52 //
53 // ===========================================================================
54
55
56 concession_t ConHintList = {};
57
58
59 /*
60 =============================================================================
61
62 STATICS
63
64 =============================================================================
65 */
66
67 statobj_t statobjlist[MAXSTATS];
68 statobj_t* laststatobj;
69 StatInfos statinfo;
70
71
initialize_static_info_constants()72 void initialize_static_info_constants()
73 {
74 statinfo = {
75 { SPR_STAT_0, bo_water_puddle, }, // Water Puddle SPR1V
76 { SPR_STAT_1, block, }, // Containment Canister
77 { SPR_STAT_2, block, }, // Lunch Table
78 { SPR_STAT_3, block, }, // Floor Lamp
79 { SPR_STAT_4, block, }, // Lab Table
80 { SPR_STAT_5, block, }, // Pillar
81 { SPR_STAT_6, dressing, }, // Blood Puddle
82 { SPR_STAT_7, dressing, }, // Piss Puddle
83
84 { SPR_STAT_8, block, }, // Ficus Tree SPR2V
85 { SPR_STAT_9, dressing, }, // Half-Eaten Corpse
86 { SPR_STAT_10, block, }, // Water Fountain
87 { SPR_STAT_11, block, }, // Plant 1
88 { SPR_STAT_12, block, }, // Vase
89 { SPR_STAT_13, block, }, // General Table
90 { SPR_STAT_14, dressing, }, // Ceiling Light
91 { SPR_STAT_15, block, }, // General Chair
92
93 { SPR_STAT_16, block, }, // Kitchen Trash SPR3V
94 { SPR_STAT_17, dressing, }, // Office Trash
95 { SPR_STAT_18, block, }, // Plant 2
96 { SPR_STAT_19, block, }, // Gurney No-Blood
97 { SPR_STAT_20, dressing, }, // Indirect Half-Sphere
98 { SPR_STAT_21, dressing, }, // Exit Sign
99 { SPR_STAT_22, dressing, }, // Transporter
100 { SPR_STAT_23, block, }, // Body Can
101
102 { SPR_STAT_24, bo_pistol, }, // PISTOL SPR4V
103 { SPR_STAT_25, block, }, // Statue
104
105 { SPR_STAT_31, bo_clip, }, // Charge Unit
106
107 { SPR_STAT_27, bo_burst_rifle, }, // Auto-Burst Rifle
108 { SPR_STAT_28, bo_ion_cannon, }, // Particle Charged ION
109 { SPR_STAT_29, bo_firstaid, }, // First Aid
110 { SPR_VSPIKE8, block, }, // Static VSPIKE
111
112 { SPR_STAT_26, bo_clip2, }, // Big Charge pack/clip
113
114 { SPR_STAT_32, bo_red_key, }, // Red Key SPR5V
115 { SPR_STAT_33, bo_yellow_key, }, // Yellow Key
116 { SPR_STAT_34, ::is_ps() ? bo_bfg_cannon : bo_green_key, }, // BFG Cannon
117 { SPR_STAT_35, bo_blue_key, }, // Blue Key
118 { SPR_STAT_36, ::is_ps() ? dressing : bo_gold_key, }, // OPEN
119 { SPR_STAT_37, block, }, // Office Desk
120 { SPR_STAT_38, block, }, // Office Chair
121 { SPR_STAT_39, block, }, // Security Desk
122
123 { SPR_STAT_40, bo_water, }, // Full Water Bowl SPR7V
124 { SPR_STAT_41, dressing, }, // Empty Water Bowl
125 { SPR_STAT_42, bo_chicken, }, // Chicken Leg
126 { SPR_STAT_43, dressing, }, // Chicken Bone
127 { SPR_STAT_44, bo_ham, }, // Ham
128 { SPR_STAT_45, dressing, }, // Ham Bone
129 { SPR_STAT_46, bo_grenade, }, // Grande Launcher
130 { SPR_STAT_47, dressing, }, // Video Game Machine
131
132 { SPR_VPOST8, block, }, // Static VPOST
133
134 // -- VARIOUS --
135
136 { SPR_GURNEY_MUT_READY, block, }, // 49 Gurney Mutant
137 { SPR_LCAN_ALIEN_READY, block, }, // 50 Large Alien Canister
138 { SPR_SCAN_ALIEN_READY, block, }, // 51 Small Alien Canister
139
140 { SPR_GURNEY_MUT_EMPTY, block, }, // 52 Gurney Mutant
141 { SPR_LCAN_ALIEN_EMPTY, block, }, // 53 Large Alien Canister
142 { SPR_SCAN_ALIEN_EMPTY, block, }, // 54 Small Alien Canister
143
144 { SPR_OFC_DEAD, dressing, }, // 55 Dead Gen Sci.
145
146 { SPR_DEMO, dressing, }, // 56 Spacer
147
148 { SPR_AIR_VENT, bo_plainvent, }, // 57 Plain air vent
149 { SPR_AIR_VENT, bo_bloodvent, }, // 58 Blood air vent
150 { SPR_AIR_VENT, bo_watervent, }, // 59 Water air vent
151 { SPR_GRATE, dressing, }, // 60 Floor Grate
152 { SPR_STEAM_PIPE, dressing, }, // 61 Steam Pipe
153
154 { SPR_STAT_48, bo_money_bag, }, // 62 money bag
155 { SPR_STAT_49, bo_loot, }, // 63 loot
156 { SPR_STAT_50, bo_gold, }, // 64 gold
157 { SPR_STAT_51, bo_bonus, }, // 65 bonus
158
159 { SPR_STAT_52, block, }, // 66 Greek Post
160 { SPR_STAT_53, block, }, // 67 Red/Blue post
161 { SPR_STAT_54, block, }, // 68 Red HiTech Post
162 { SPR_STAT_55, dressing, }, // 69 Ceiling Lamp #2
163 { SPR_STAT_56, dressing, }, // 70 Ceiling Lamp #3
164 { SPR_STAT_57, dressing, }, // 71 Body Parts
165 { SPR_STAT_58, dressing, }, // 72 OR Lamp
166 { SPR_STAT_59, block, }, // 73 Office Sink
167 { SPR_STAT_57, dressing, }, // EMPTY - Copy of 71 - Body Parts...
168 { SPR_CANDY_BAR, bo_candybar, }, // 75 candy bar
169 { SPR_SANDWICH, bo_sandwich, }, // 76 sandwich
170 { SPR_CRATE_1, block, }, // 77 Crate #1
171 { SPR_CRATE_2, block, }, // 78 Crate #2
172 { SPR_CRATE_3, block, }, // 79 Crate #3
173 { SPR_STAT_61, block, }, // 80 Table
174 { SPR_STAT_62, block, }, // 81 Chair
175 { SPR_STAT_63, block, }, // 82 Stool
176 { SPR_STAT_64, dressing, }, // 83 Gore
177
178 { SPR_STAT_65, bo_gold3, }, // Gold 3
179 { SPR_STAT_66, bo_gold2, }, // Gold 2
180 { SPR_STAT_67, bo_gold1, }, // Gold 1
181
182 { SPR_STAT_68, block, }, //
183 { SPR_STAT_69, block, }, //
184 { SPR_STAT_70, block, }, //
185 { SPR_STAT_71, block, }, //
186 { SPR_STAT_72, block, }, //
187 { SPR_STAT_73, dressing, }, //
188 { SPR_STAT_74, dressing, }, //
189 { SPR_STAT_75, dressing, }, //
190 { SPR_STAT_76, dressing, }, //
191
192 { SPR_RENT_DEAD, dressing, }, //
193 { SPR_PRO_DEAD, dressing, }, //
194 { SPR_SWAT_DEAD, dressing, }, //
195 { SPR_GSCOUT_DEAD, dressing, }, //
196 { SPR_FSCOUT_DEAD, dressing, }, //
197 { SPR_MUTHUM1_DEAD, dressing, },
198 { SPR_MUTHUM2_DEAD, dressing, },
199 { SPR_LCAN_ALIEN_DEAD, dressing, },
200 { SPR_SCAN_ALIEN_DEAD, dressing, },
201 { SPR_GURNEY_MUT_DEAD, dressing, },
202 { SPR_TERROT_DEAD, dressing, },
203 { SPR_POD_DIE3, dressing, },
204 { SPR_STAT_77, bo_coin, }, // Concession Machine Money
205 { SPR_STAT_78, bo_coin5, }, // Concession Machine Money
206 { SPR_STAT_79, dressing, }, // Auto-Charge Pistol
207
208 { SPR_DOORBOMB, bo_plasma_detonator, }, // Plasma Detonator
209 { SPR_RUBBLE, dressing, }, // Door Rubble
210 { SPR_AUTOMAPPER, bo_automapper1, }, // Auto Mapper Bonus #1
211 { SPR_BONZI_TREE, block, }, // BonziTree
212 { SPR_POT_PLANT, block, }, // Yellow Potted Plant
213 { SPR_TUBE_PLANT, block, }, // Tube Plant
214 { SPR_HITECH_CHAIR, block, }, // Hi Tech table and chair
215 { SPR_DEAD_RENT, dressing, }, // Dead AOG: Rent A Cop
216 { SPR_DEAD_PRO, dressing, }, // Dead AOG: Pro Guard
217 { SPR_DEAD_SWAT, dressing, }, // Dead AOG: Swat Guad
218
219 { -1, dressing, }, // terminator
220 };
221 }
222
InitStaticList()223 void InitStaticList()
224 {
225 laststatobj = &statobjlist[0];
226 }
227
228 // ---------------------------------------------------------------------------
229 // FindStatic()
230 //
231 // FUNCTION: Searches the stat obj list and returns ptr to a static obj
232 // at a particular tile x & tile y coords.
233 //
234 // RETURNS: Ptr == Pointer to static obj.
235 // NULL == No static found.
236 // ---------------------------------------------------------------------------
FindStatic(uint16_t tilex,uint16_t tiley)237 statobj_t* FindStatic(
238 uint16_t tilex,
239 uint16_t tiley)
240 {
241 statobj_t* spot;
242
243 for (spot = statobjlist; spot != laststatobj; spot++) {
244 if (spot->shapenum != -1 && spot->tilex == tilex && spot->tiley == tiley) {
245 return spot;
246 }
247 }
248
249 return nullptr;
250 }
251
252 // ---------------------------------------------------------------------------
253 // FindEmptyStatic()
254 //
255 // FUNCTION: Searches the stat obj list and returns ptr to an empty
256 // static object.
257 //
258 // RETURNS: Ptr == Pointer to empty static obj.
259 // NULL == static objlist full.
260 // ---------------------------------------------------------------------------
FindEmptyStatic()261 statobj_t* FindEmptyStatic()
262 {
263 statobj_t* spot;
264
265 for (spot = &statobjlist[0];; spot++) {
266 if (spot == laststatobj) {
267 if (spot == &statobjlist[MAXSTATS]) {
268 return nullptr;
269 }
270 laststatobj++; // space at end
271 break;
272 }
273
274 if (spot->shapenum == -1) { // -1 is a free spot
275 break;
276 }
277 }
278
279 return spot;
280 }
281
SpawnStatic(int16_t tilex,int16_t tiley,int16_t type)282 void SpawnStatic(
283 int16_t tilex,
284 int16_t tiley,
285 int16_t type)
286 {
287 statobj_t* spot;
288
289 spot = FindEmptyStatic();
290
291 if (!spot) {
292 return;
293 }
294
295 spot->shapenum = statinfo[type].picnum;
296 spot->tilex = static_cast<uint8_t>(tilex);
297 spot->tiley = static_cast<uint8_t>(tiley);
298 spot->visspot = &spotvis[tilex][tiley];
299 spot->flags = 0;
300
301 if ((!::is_aog_sw() && spot->shapenum == SPR_STAT_3) || // // floor lamp
302 spot->shapenum == SPR_STAT_14 || // ceiling light
303 (!::is_aog_sw() && spot->shapenum == SPR_STAT_20) ||
304 spot->shapenum == SPR_STAT_47 ||
305 spot->shapenum == SPR_STAT_51 ||
306 spot->shapenum == SPR_STAT_55 ||
307 spot->shapenum == SPR_STAT_56)
308 {
309 spot->lighting = LAMP_ON_SHADING;
310 } else {
311 spot->lighting = 0;
312 }
313
314
315 switch (statinfo[type].type) {
316 case block:
317 // consider it a blocking tile
318 actorat[tilex][tiley] = (objtype*)1;
319 break;
320
321 case bo_green_key:
322 case bo_gold_key:
323 if (::is_ps()) {
324 ::Quit("Green/Gold key (AOG) at ({}, {}).", tilex, tiley);
325 }
326 TravelTable[tilex][tiley] |= TT_KEYS;
327 spot->flags = FL_BONUS;
328 spot->itemnumber = static_cast<uint8_t>(statinfo[type].type);
329 break;
330
331 case bo_plasma_detonator:
332 if (!::is_ps()) {
333 ::Quit("Plasma detonator (PS) at ({}, {}).", tilex, tiley);
334 }
335 TravelTable[tilex][tiley] |= TT_KEYS;
336 spot->flags = FL_BONUS;
337 spot->itemnumber = static_cast<uint8_t>(statinfo[type].type);
338 break;
339
340 case bo_red_key:
341 case bo_yellow_key:
342 case bo_blue_key:
343 TravelTable[tilex][tiley] |= TT_KEYS;
344
345 case bo_gold1:
346 case bo_gold2:
347 case bo_gold3:
348 case bo_gold:
349 case bo_bonus:
350 case bo_money_bag:
351 case bo_loot:
352
353 case bo_fullheal:
354 case bo_firstaid:
355 case bo_clip:
356 case bo_clip2:
357 case bo_burst_rifle:
358 case bo_ion_cannon:
359 case bo_grenade:
360 case bo_bfg_cannon:
361 case bo_pistol:
362 case bo_chicken:
363 case bo_ham:
364 case bo_water:
365 case bo_water_puddle:
366 case bo_sandwich:
367 case bo_candybar:
368 case bo_coin:
369 case bo_coin5:
370 case bo_automapper1:
371 spot->flags = FL_BONUS;
372 spot->itemnumber = static_cast<uint8_t>(statinfo[type].type);
373 break;
374
375 default:
376 break;
377 }
378
379 spot->areanumber = GetAreaNumber(spot->tilex, spot->tiley);
380
381 spot++;
382
383 if (spot == &statobjlist[MAXSTATS]) {
384 ::Quit("Too many static objects.");
385 }
386 }
387
388 // ---------------------------------------------------------------------------
389 // ReserveStatic()
390 //
391 // Reserves a static object at location 0,0 (unseen). This function is
392 // used to gaurantee that a static will be available.
393 // ---------------------------------------------------------------------------
ReserveStatic()394 statobj_t* ReserveStatic()
395 {
396 auto spot = FindEmptyStatic();
397
398 if (!spot) {
399 ::Quit("Too many static objects.");
400 }
401
402 // Mark as Used.
403
404 spot->shapenum = 1;
405 spot->tilex = 0;
406 spot->tiley = 0;
407 spot->visspot = &spotvis[0][0];
408
409 return spot;
410 }
411
412 // ---------------------------------------------------------------------------
413 // FindReservedStatic()
414 //
415 // Finds a Reserved static object at location 0,0 (unseen). This function is
416 // used to gaurantee that a static will be available.
417 // ---------------------------------------------------------------------------
FindReservedStatic()418 statobj_t* FindReservedStatic()
419 {
420 statobj_t* spot;
421
422 for (spot = &statobjlist[0]; spot < &statobjlist[MAXSTATS]; spot++) {
423 if (spot->shapenum == 1 && (!spot->tilex) && (!spot->tiley)) { // -1 is a free spot
424 return spot;
425 }
426 }
427
428 return nullptr;
429 }
430
431 // ---------------------------------------------------------------------------
432 // UseReservedStatic()
433 //
434 // Finds a Reserved static object and moves it to a new location with new
435 // attributes.
436 //
437 // This function acts like PlaceItemType - But REQUIRES a reserved
438 // static. Before using this function, make sure that you have already
439 // reserved a static to be used using ReserveStatic();
440 // ---------------------------------------------------------------------------
UseReservedStatic(int16_t itemtype,int16_t tilex,int16_t tiley)441 statobj_t* UseReservedStatic(
442 int16_t itemtype,
443 int16_t tilex,
444 int16_t tiley)
445 {
446 auto spot = FindReservedStatic();
447 int16_t type;
448
449 if (!spot) {
450 ::Quit("Count not find a reserved static at location (0, 0) with shape #1.");
451 }
452
453 //
454 // find the item number
455 //
456
457 for (type = 0;; type++) {
458 if (statinfo[type].picnum == -1) { // End of Static List...
459 ::Quit("Couldn't find type.");
460 }
461
462 if (statinfo[type].type == itemtype) { // Bingo, Found it!
463 break;
464 }
465 }
466
467 //
468 // place it
469 //
470
471 switch (type) {
472 case bo_green_key:
473 case bo_gold_key:
474 case bo_red_key:
475 case bo_yellow_key:
476 case bo_blue_key:
477 TravelTable[tilex][tiley] |= TT_KEYS;
478 break;
479 }
480
481 spot->shapenum = statinfo[type].picnum;
482 spot->tilex = static_cast<uint8_t>(tilex);
483 spot->tiley = static_cast<uint8_t>(tiley);
484 spot->visspot = &spotvis[tilex][tiley];
485 spot->flags = FL_BONUS;
486 spot->itemnumber = static_cast<uint8_t>(statinfo[type].type);
487
488 spot->areanumber = GetAreaNumber(spot->tilex, spot->tiley);
489
490 return spot;
491 }
492
493 int8_t pint_xy[8][2] = {
494 { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 },
495 { 0, 1 }, { 1, -1 }, { 1, 0 }, { 1, 1 },
496 };
497
PlaceReservedItemNearTile(int16_t itemtype,int16_t tilex,int16_t tiley)498 void PlaceReservedItemNearTile(
499 int16_t itemtype,
500 int16_t tilex,
501 int16_t tiley)
502 {
503 int8_t loop;
504
505 for (loop = 0; loop < 8; loop++) {
506 int8_t x = static_cast<int8_t>(tilex + pint_xy[static_cast<int>(loop)][1]), y = static_cast<int8_t>(tiley + pint_xy[static_cast<int>(loop)][0]);
507
508 if (!tilemap[static_cast<int>(x)][static_cast<int>(y)]) {
509 if (actorat[static_cast<int>(x)][static_cast<int>(y)] == reinterpret_cast<objtype*>(1)) { // Check for a SOLID static
510 continue;
511 }
512
513 UseReservedStatic(itemtype, x, y);
514 return;
515 }
516 }
517
518 UseReservedStatic(itemtype, tilex, tiley);
519 }
520
521 /*
522 ===============
523 =
524 = PlaceItemType
525 =
526 = Called during game play to drop actors' items. It finds the proper
527 = item number based on the item type (bo_???). If there are no free item
528 = spots, nothing is done.
529 =
530 ===============
531 */
PlaceItemType(int16_t itemtype,int16_t tilex,int16_t tiley)532 void PlaceItemType(
533 int16_t itemtype,
534 int16_t tilex,
535 int16_t tiley)
536 {
537 int16_t type;
538 statobj_t* spot;
539
540 //
541 // find the item number
542 //
543 for (type = 0;; type++) {
544 if (statinfo[type].picnum == -1) { // end of list
545 ::Quit("Couldn't find type.");
546 }
547 if (statinfo[type].type == itemtype) {
548 break;
549 }
550 }
551
552 //
553 // find a spot in statobjlist to put it in
554 //
555 spot = FindEmptyStatic();
556
557 if (!spot) {
558 return;
559 }
560
561 //
562 // place it
563 //
564 spot->shapenum = statinfo[type].picnum;
565 spot->tilex = static_cast<uint8_t>(tilex);
566 spot->tiley = static_cast<uint8_t>(tiley);
567 spot->visspot = &spotvis[tilex][tiley];
568 spot->flags = FL_BONUS;
569 spot->itemnumber = static_cast<uint8_t>(statinfo[type].type);
570
571 spot->areanumber = GetAreaNumber(spot->tilex, spot->tiley);
572 }
573
PlaceItemNearTile(int16_t itemtype,int16_t tilex,int16_t tiley)574 void PlaceItemNearTile(
575 int16_t itemtype,
576 int16_t tilex,
577 int16_t tiley)
578 {
579 // [0] is the y offset
580 // [1] is the x offset
581 //
582 int8_t loop;
583
584 for (loop = 0; loop < 8; loop++) {
585 int8_t x = static_cast<int8_t>(tilex + pint_xy[static_cast<int>(loop)][1]), y = static_cast<int8_t>(tiley + pint_xy[static_cast<int>(loop)][0]);
586
587 if (!tilemap[static_cast<int>(x)][static_cast<int>(y)]) {
588 if (actorat[static_cast<int>(x)][static_cast<int>(y)] == reinterpret_cast<objtype*>(1)) { // Check for a SOLID static
589 continue;
590 }
591
592 PlaceItemType(itemtype, x, y);
593 return;
594 }
595 }
596
597 PlaceItemType(itemtype, tilex, tiley);
598 }
599
600 // --------------------------------------------------------------------------
601 // ExplodeStatics()
602 //
603 // NOTES: Explodes statics in a one tile radius from a given tile x and tile y
604 //
605 // --------------------------------------------------------------------------
ExplodeStatics(int16_t tilex,int16_t tiley)606 void ExplodeStatics(
607 int16_t tilex,
608 int16_t tiley)
609 {
610 if (!::is_ps()) {
611 return;
612 }
613
614 statobj_t* spot;
615 int16_t y_diff, x_diff;
616 bool remove;
617
618 for (spot = &statobjlist[0]; spot != laststatobj; spot++) {
619 if (spot->shapenum != -1) {
620 y_diff = spot->tiley - tiley;
621 y_diff = ABS(y_diff);
622
623 x_diff = spot->tilex - tilex;
624 x_diff = ABS(x_diff);
625
626 if (x_diff < 2 && y_diff < 2) {
627 remove = false;
628
629 //
630 // Test for specific statics..
631 //
632
633 switch (spot->itemnumber) {
634 //
635 // Check for Clips
636 //
637 case bo_clip:
638 case bo_clip2:
639 remove = true;
640 SpawnCusExplosion((((fixed)spot->tilex) << TILESHIFT) + 0x7FFF,
641 (((fixed)spot->tiley) << TILESHIFT) + 0x7FFF,
642 SPR_CLIP_EXP1, 7, 3 + (US_RndT() & 0x3), explosionobj);
643 break;
644 }
645
646
647 //
648 // Check to see if we remove it.
649 //
650 if (remove) {
651 // Remove static
652 spot->shapenum = -1;
653 spot->itemnumber = bo_nothing;
654 }
655 }
656 }
657 }
658 }
659
660 /*
661 =============================================================================
662
663 DOORS
664
665 doorobjlist[] holds most of the information for the doors
666
667 doorposition[] holds the amount the door is open, ranging from 0 to 0xffff
668 this is directly accessed by AsmRefresh during rendering
669
670 The number of doors is limited to 64 because a spot in tilemap holds the
671 door number in the low 6 bits, with the high bit meaning a door center
672 and bit 6 meaning a door side tile
673
674 Open doors conect two areas, so sounds will travel between them and sight
675 will be checked when the player is in a connected area.
676
677 Areaconnect is incremented/decremented by each door. If >0 they connect
678
679 Every time a door opens or closes the areabyplayer matrix gets recalculated.
680 An area is true if it connects with the player's current spor.
681
682 =============================================================================
683 */
684
685 static const int16_t OPENTICS = 300;
686
687 doorobj_t doorobjlist[MAXDOORS];
688 doorobj_t* lastdoorobj;
689 int16_t doornum;
690
691 // leading edge of door 0=closed, 0xffff = fully open
692 uint16_t doorposition[MAXDOORS];
693
694 uint8_t areaconnect[NUMAREAS][NUMAREAS];
695
696 bool areabyplayer[NUMAREAS];
697
698
699 /*
700 ==============
701 =
702 = ConnectAreas
703 =
704 = Scans outward from playerarea, marking all connected areas
705 =
706 ==============
707 */
RecursiveConnect(int16_t areanumber)708 void RecursiveConnect(
709 int16_t areanumber)
710 {
711 int16_t i;
712
713 for (i = 0; i < NUMAREAS; i++) {
714 if (areaconnect[areanumber][i] && !areabyplayer[i]) {
715 areabyplayer[i] = true;
716 RecursiveConnect(i);
717 }
718 }
719 }
720
ConnectAreas()721 void ConnectAreas()
722 {
723 memset(areabyplayer, 0, sizeof(areabyplayer));
724 areabyplayer[player->areanumber] = true;
725 RecursiveConnect(player->areanumber);
726 }
727
InitAreas()728 void InitAreas()
729 {
730 memset(areabyplayer, 0, sizeof(areabyplayer));
731 areabyplayer[player->areanumber] = true;
732 }
733
InitDoorList()734 void InitDoorList()
735 {
736 memset(areabyplayer, 0, sizeof(areabyplayer));
737 memset(areaconnect, 0, sizeof(areaconnect));
738
739 lastdoorobj = &doorobjlist[0];
740 doornum = 0;
741 }
742
SpawnDoor(int16_t tilex,int16_t tiley,bool vertical,keytype lock,door_t type)743 void SpawnDoor(
744 int16_t tilex,
745 int16_t tiley,
746 bool vertical,
747 keytype lock,
748 door_t type)
749 {
750 uint16_t* map[2];
751
752 map[0] = mapsegs[0] + farmapylookup[tiley] + tilex;
753 map[1] = mapsegs[1] + farmapylookup[tiley] + tilex;
754
755 if (doornum == 64) {
756 ::Quit("Too many doors in level.");
757 }
758
759 doorposition[doornum] = 0; // doors start out fully closed
760 lastdoorobj->tilex = static_cast<uint8_t>(tilex);
761 lastdoorobj->tiley = static_cast<uint8_t>(tiley);
762 lastdoorobj->vertical = vertical;
763 lastdoorobj->lock = lock;
764 lastdoorobj->type = type;
765 lastdoorobj->action = dr_closed;
766 lastdoorobj->flags = DR_BLASTABLE; // JIM - Do something with this! jdebug
767
768 if (vertical) {
769 lastdoorobj->areanumber[0] = GetAreaNumber(static_cast<int8_t>(tilex + 1), static_cast<int8_t>(tiley));
770 lastdoorobj->areanumber[1] = GetAreaNumber(static_cast<int8_t>(tilex - 1), static_cast<int8_t>(tiley));
771 } else {
772 lastdoorobj->areanumber[0] = GetAreaNumber(static_cast<int8_t>(tilex), static_cast<int8_t>(tiley - 1));
773 lastdoorobj->areanumber[1] = GetAreaNumber(static_cast<int8_t>(tilex), static_cast<int8_t>(tiley + 1));
774 }
775
776 // consider it a solid wall
777 actorat[tilex][tiley] = reinterpret_cast<objtype*>(static_cast<size_t>(doornum | 0x80));
778
779 //
780 // make the door tile a special tile, and mark the adjacent tiles
781 // for door sides
782 //
783 tilemap[tilex][tiley] = static_cast<uint8_t>(doornum | 0x80);
784
785 if (vertical) {
786 if (*(map[0] - mapwidth - 1) == TRANSPORTERTILE) {
787 *map[0] = GetAreaNumber(static_cast<int8_t>(tilex + 1), static_cast<int8_t>(tiley));
788 } else {
789 *map[0] = GetAreaNumber(static_cast<int8_t>(tilex - 1), static_cast<int8_t>(tiley));
790 }
791 tilemap[tilex][tiley - 1] |= 0x40;
792 tilemap[tilex][tiley + 1] |= 0x40;
793 } else {
794 *map[0] = GetAreaNumber(static_cast<int8_t>(tilex), static_cast<int8_t>(tiley - 1));
795 tilemap[tilex - 1][tiley] |= 0x40;
796 tilemap[tilex + 1][tiley] |= 0x40;
797 }
798
799 doornum++;
800 lastdoorobj++;
801 }
802
CheckLinkedDoors(int16_t door,int16_t door_dir)803 void CheckLinkedDoors(
804 int16_t door,
805 int16_t door_dir)
806 {
807 static int16_t LinkCheck = 0;
808 static int16_t base_tilex;
809 static int16_t base_tiley;
810
811 int16_t tilex = doorobjlist[door].tilex;
812 int16_t tiley = doorobjlist[door].tiley;
813 int16_t next_tilex = 0;
814 int16_t next_tiley = 0;
815
816 // Find next door in link.
817 //
818 if (*(mapsegs[1] + (farmapylookup[tiley] + tilex))) {
819 uint16_t value = *(mapsegs[1] + (farmapylookup[tiley] + tilex));
820
821 // Define the next door in the link.
822 //
823 next_tilex = (value & 0xff00) >> 8;
824 next_tiley = value & 0xff;
825
826 // Is this the head of the link?
827 //
828 if (!LinkCheck) {
829 base_tilex = tilex;
830 base_tiley = tiley;
831 }
832 }
833
834 LinkCheck++;
835
836 // Recursively open/close linked doors.
837 //
838 if ((next_tilex) &&
839 (next_tiley) &&
840 ((next_tilex != base_tilex) || (next_tiley != base_tiley))
841 )
842 {
843 int16_t door_index = tilemap[next_tilex][next_tiley] & ~0x80;
844
845 switch (door_dir) {
846 case dr_opening:
847 doorobjlist[door_index].lock = kt_none;
848 OpenDoor(door_index);
849 break;
850
851 case dr_closing:
852 doorobjlist[door_index].lock = kt_none;
853 CloseDoor(door_index);
854 break;
855 }
856 }
857
858 LinkCheck--;
859 }
860
OpenDoor(int16_t door)861 void OpenDoor(
862 int16_t door)
863 {
864
865 if (doorobjlist[door].action == dr_jammed) {
866 return;
867 }
868
869 if (doorobjlist[door].action == dr_open) {
870 doorobjlist[door].ticcount = 0; // reset open time
871 } else {
872 doorobjlist[door].action = dr_opening; // start it opening
873
874 }
875 CheckLinkedDoors(door, dr_opening);
876 }
877
get_actor_near_door(int tile_x,int tile_y)878 objtype* get_actor_near_door(
879 int tile_x,
880 int tile_y)
881 {
882 if (::is_aog()) {
883 for (int i = 0; i < doornum; ++i) {
884 const doorobj_t& door = doorobjlist[i];
885
886 if (door.tilex == tile_x && door.tiley == tile_y) {
887 // It's a closing door, not an actor.
888 return nullptr;
889 }
890 }
891 }
892
893 return actorat[tile_x][tile_y];
894 }
895
CloseDoor(int16_t door)896 void CloseDoor(
897 int16_t door)
898 {
899 int16_t tilex, tiley, area;
900 objtype* check;
901
902 if (doorobjlist[door].action == dr_jammed) {
903 return;
904 }
905
906 //
907 // don't close on anything solid
908 //
909 tilex = doorobjlist[door].tilex;
910 tiley = doorobjlist[door].tiley;
911
912 if (actorat[tilex][tiley]) {
913 return;
914 }
915
916 if (player->tilex == tilex && player->tiley == tiley) {
917 return;
918 }
919
920 if (doorobjlist[door].vertical) {
921 if (player->tiley == tiley) {
922 if (((player->x + MINDIST) >> TILESHIFT) == tilex) {
923 return;
924 }
925 if (((player->x - MINDIST) >> TILESHIFT) == tilex) {
926 return;
927 }
928 }
929 check = ::get_actor_near_door(tilex - 1, tiley);
930 if (check && ((check->x + MINDIST) >> TILESHIFT) == tilex) {
931 return;
932 }
933 check = ::get_actor_near_door(tilex + 1, tiley);
934 if (check && ((check->x - MINDIST) >> TILESHIFT) == tilex) {
935 return;
936 }
937 } else if (!doorobjlist[door].vertical) {
938 if (player->tilex == tilex) {
939 if (((player->y + MINDIST) >> TILESHIFT) == tiley) {
940 return;
941 }
942 if (((player->y - MINDIST) >> TILESHIFT) == tiley) {
943 return;
944 }
945 }
946 check = ::get_actor_near_door(tilex, tiley - 1);
947 if (check && ((check->y + MINDIST) >> TILESHIFT) == tiley) {
948 return;
949 }
950 check = ::get_actor_near_door(tilex, tiley + 1);
951 if (check && ((check->y - MINDIST) >> TILESHIFT) == tiley) {
952 return;
953 }
954 }
955
956
957 //
958 // play door sound if in a connected area
959 //
960 area = GetAreaNumber(doorobjlist[door].tilex, doorobjlist[door].tiley);
961 if (areabyplayer[area]) {
962 switch (doorobjlist[door].type) {
963 case dr_bio:
964 case dr_office:
965 case dr_space:
966 case dr_normal:
967 ::sd_play_door_sound(
968 HTECHDOORCLOSESND, &doorobjlist[door]);
969 break;
970
971 default:
972 ::sd_play_door_sound(CLOSEDOORSND, &doorobjlist[door]);
973 break;
974 }
975 }
976
977 doorobjlist[door].action = dr_closing;
978 //
979 // make the door space solid
980 //
981
982 actorat[tilex][tiley] = reinterpret_cast<objtype*>(static_cast<size_t>(door | 0x80));
983
984 CheckLinkedDoors(door, dr_closing);
985
986 }
987
988 /*
989 =====================
990 =
991 = OperateDoor
992 =
993 = The player wants to change the door's direction
994 =
995 =====================
996 */
997
998 const char* const od_oneway = "\r\r DOOR LOCKED FROM\r THIS SIDE.\r^XX";
999 const char* const od_locked = "\r\r DOOR PERMANENTLY\r LOCKED.\r^XX";
1000 const char* const od_reddenied = "\r\r RED LEVEL\r ACCESS DENIED!\r^XX";
1001 const char* const od_yellowdenied = "\r\r YELLOW LEVEL\r ACCESS DENIED!\r^XX";
1002 const char* const od_bluedenied = "\r\r BLUE LEVEL\r ACCESS DENIED!\r^XX";
1003
1004 const char* const od_green_denied =
1005 "\r"
1006 "\r"
1007 " GREEN LEVEL\r"
1008 " ACCESS DENIED!\r"
1009 "^XX"
1010 ;
1011
1012 const char* const od_gold_denied =
1013 "\r"
1014 "\r"
1015 " GOLD LEVEL\r"
1016 " ACCESS DENIED!\r"
1017 "^XX"
1018 ;
1019
1020 const char* const od_granted = "\r\r ACCESS GRANTED\r DOOR UNLOCKED.\r^XX";
1021 const char* const od_operating = "\r\r OPERATING DOOR.\r^XX";
1022
OperateDoor(int16_t door)1023 void OperateDoor(
1024 int16_t door)
1025 {
1026 int16_t lock;
1027 bool oneway = false;
1028
1029
1030 //
1031 // Check for wrong way on a ONEWAY door.
1032 //
1033
1034 switch (doorobjlist[door].type) {
1035 case dr_oneway_left:
1036 if (player->tilex < doorobjlist[door].tilex) {
1037 oneway = true;
1038 }
1039 break;
1040
1041 case dr_oneway_right:
1042 if (player->tilex > doorobjlist[door].tilex) {
1043 oneway = true;
1044 }
1045 break;
1046
1047 case dr_oneway_up:
1048 if (player->tiley < doorobjlist[door].tiley) {
1049 oneway = true;
1050 }
1051 break;
1052
1053 case dr_oneway_down:
1054 if (player->tiley > doorobjlist[door].tiley) {
1055 oneway = true;
1056 }
1057 break;
1058
1059 default:
1060 break;
1061 }
1062
1063 if (oneway) {
1064 if (doorobjlist[door].action == dr_closed) {
1065 DISPLAY_TIMED_MSG(od_oneway, MP_DOOR_OPERATE, MT_GENERAL);
1066 ::sd_play_player_sound(NOWAYSND, bstone::AC_NO_WAY);
1067 }
1068
1069 return;
1070 }
1071
1072 //
1073 // Check for possibly being locked
1074 //
1075
1076 lock = static_cast<int16_t>(doorobjlist[door].lock);
1077 if (lock != kt_none) {
1078 if (!(gamestate.numkeys[lock - kt_red])) {
1079 ::sd_play_player_sound(NOWAYSND, bstone::AC_NO_WAY);
1080
1081 switch (lock) {
1082 case kt_red:
1083 DISPLAY_TIMED_MSG(od_reddenied, MP_DOOR_OPERATE, MT_GENERAL);
1084 break;
1085
1086 case kt_yellow:
1087 DISPLAY_TIMED_MSG(od_yellowdenied, MP_DOOR_OPERATE, MT_GENERAL);
1088 break;
1089
1090 case kt_blue:
1091 DISPLAY_TIMED_MSG(od_bluedenied, MP_DOOR_OPERATE, MT_GENERAL);
1092 break;
1093
1094 case kt_green:
1095 DISPLAY_TIMED_MSG(od_green_denied, MP_DOOR_OPERATE, MT_GENERAL);
1096 break;
1097
1098 case kt_gold:
1099 DISPLAY_TIMED_MSG(od_gold_denied, MP_DOOR_OPERATE, MT_GENERAL);
1100 break;
1101
1102 default:
1103 DISPLAY_TIMED_MSG(od_locked, MP_DOOR_OPERATE, MT_GENERAL);
1104 break;
1105 }
1106
1107 return;
1108 } else {
1109 TakeKey(lock - kt_red);
1110 DISPLAY_TIMED_MSG(od_granted, MP_DOOR_OPERATE, MT_GENERAL);
1111 doorobjlist[door].lock = kt_none; // UnLock door
1112 }
1113 } else {
1114 DISPLAY_TIMED_MSG(od_operating, MP_DOOR_OPERATE, MT_GENERAL);
1115 }
1116
1117 switch (doorobjlist[door].action) {
1118 case dr_closed:
1119 case dr_closing:
1120 OpenDoor(door);
1121 break;
1122
1123 case dr_open:
1124 case dr_opening:
1125 CloseDoor(door);
1126 break;
1127 default:
1128 break;
1129 }
1130 }
1131
BlockDoorOpen(int16_t door)1132 void BlockDoorOpen(
1133 int16_t door)
1134 {
1135 doorobjlist[door].action = dr_jammed;
1136 doorobjlist[door].ticcount = 0;
1137 doorposition[door] = 0xFFFF;
1138 doorobjlist[door].lock = kt_none;
1139 doorobjlist[door].flags &= ~DR_BLASTABLE;
1140
1141 actorat[doorobjlist[door].tilex][doorobjlist[door].tiley] = 0;
1142
1143 TransformAreas(doorobjlist[door].tilex, doorobjlist[door].tiley, 1);
1144 }
1145
TryBlastDoor(int8_t door)1146 void TryBlastDoor(
1147 int8_t door)
1148 {
1149 switch (doorobjlist[static_cast<int>(door)].type) {
1150 case dr_oneway_left:
1151 case dr_oneway_up:
1152 case dr_oneway_right:
1153 case dr_oneway_down:
1154 break;
1155
1156 default:
1157 if (doorposition[static_cast<int>(door)] < 0x7fff &&
1158 doorobjlist[static_cast<int>(door)].action != dr_jammed &&
1159 doorobjlist[static_cast<int>(door)].lock == kt_none)
1160 {
1161 BlockDoorOpen(door);
1162 SpawnCusExplosion((((fixed)doorobjlist[static_cast<int>(door)].tilex) << TILESHIFT) + 0x7FFF,
1163 (((fixed)doorobjlist[static_cast<int>(door)].tiley) << TILESHIFT) + 0x7FFF,
1164 SPR_EXPLOSION_1, 4, 3, doorexplodeobj);
1165 }
1166 break;
1167 }
1168 }
1169
BlastNearDoors(int16_t tilex,int16_t tiley)1170 void BlastNearDoors(
1171 int16_t tilex,
1172 int16_t tiley)
1173 {
1174 uint8_t door;
1175 char* doorptr;
1176 int16_t x, y;
1177
1178 doorptr = (char*)&tilemap[tilex][tiley];
1179
1180 for (x = -1; x < 2; x++) {
1181 for (y = -64; y < 128; y += 64) {
1182 if ((door = *(doorptr + x + y)) & 0x80) {
1183 door &= ~0x80;
1184 TryBlastDoor(door);
1185 }
1186 }
1187 }
1188 }
1189
DropPlasmaDetonator()1190 void DropPlasmaDetonator()
1191 {
1192 auto obj = ::MoveHiddenOfs(
1193 plasma_detonator_reserveobj,
1194 plasma_detonatorobj,
1195 player->x,
1196 player->y);
1197
1198 if (obj) {
1199 obj->flags |= FL_SHOOTABLE;
1200
1201 DISPLAY_TIMED_MSG(pd_dropped, MP_DOOR_OPERATE, MT_GENERAL);
1202 ::sd_play_actor_sound(ROBOT_SERVOSND, obj, bstone::AC_VOICE);
1203
1204 TakePlasmaDetonator(1);
1205 return;
1206 }
1207
1208 ::Quit("Could not find Fision/Plasma Detonator reserve object.");
1209 }
1210
1211 // --------------------------------------------------------------------------
1212 // TryDropPlasmaDetonator() - Will check to see if player is close enough to
1213 // drop a detonator.
1214 // --------------------------------------------------------------------------
TryDropPlasmaDetonator()1215 void TryDropPlasmaDetonator()
1216 {
1217 const int16_t MAX_RANGE_DIST = 2;
1218
1219 objtype* obj;
1220 int16_t distx, disty, distance;
1221
1222
1223 if (!gamestuff.level[gamestate.mapon + 1].locked) {
1224 DISPLAY_TIMED_MSG(pd_floornotlocked, MP_DETONATOR, MT_GENERAL);
1225 return;
1226 }
1227
1228 if (gamestate.mapon > 19) {
1229 DISPLAY_TIMED_MSG(pd_no_computer, MP_DETONATOR, MT_GENERAL);
1230 return;
1231 }
1232
1233 if (!gamestate.plasma_detonators) {
1234 DISPLAY_TIMED_MSG(pd_donthaveany, MP_DETONATOR, MT_GENERAL);
1235 return;
1236 }
1237
1238 obj = FindObj(rotating_cubeobj, -1, -1);
1239
1240 if (!obj) {
1241 ::Quit("Cound not find security cube - Need to have one pal!");
1242 }
1243
1244 if (obj->areanumber != player->areanumber) {
1245 DISPLAY_TIMED_MSG(pd_notnear, MP_DETONATOR, MT_GENERAL);
1246 return;
1247 }
1248
1249 distx = player->tilex - obj->tilex;
1250 distx = ABS(distx);
1251 disty = player->tiley - obj->tiley;
1252 disty = ABS(disty);
1253 distance = distx > disty ? distx : disty;
1254
1255 if (distance > MAX_RANGE_DIST) {
1256 DISPLAY_TIMED_MSG(pd_getcloser, MP_DETONATOR, MT_GENERAL);
1257 return;
1258 } else {
1259 DropPlasmaDetonator();
1260 }
1261 }
1262
1263 /*
1264 ===============
1265 =
1266 = DoorOpen
1267 =
1268 = Close the door after three seconds
1269 =
1270 ===============
1271 */
DoorOpen(int16_t door)1272 void DoorOpen(
1273 int16_t door)
1274 {
1275 if ((doorobjlist[door].ticcount += tics) >= OPENTICS) {
1276 CloseDoor(door);
1277 }
1278 }
1279
TransformAreas(int8_t tilex,int8_t tiley,int8_t xform)1280 int16_t TransformAreas(
1281 int8_t tilex,
1282 int8_t tiley,
1283 int8_t xform)
1284 {
1285 int16_t xofs = 0, yofs = 0;
1286 uint8_t area1, area2;
1287
1288 // Is this walkway: Horizontal? Vertical? Error?
1289 //
1290 if ((tilemap[static_cast<int>(tilex)][static_cast<int>(tiley + 1)]) && (tilemap[static_cast<int>(tilex)][static_cast<int>(tiley - 1)])) {
1291 xofs = 1;
1292 yofs = 0;
1293 } else if ((tilemap[static_cast<int>(tilex + 1)][static_cast<int>(tiley)]) && (tilemap[static_cast<int>(tilex - 1)][static_cast<int>(tiley)])) {
1294 xofs = 0;
1295 yofs = 1;
1296 } else {
1297 ::Quit("Invalid linkable area.");
1298 }
1299
1300 // Define the two areas...
1301 //
1302 area1 = GetAreaNumber(static_cast<int8_t>(tilex + xofs), static_cast<int8_t>(tiley + yofs));
1303 if (area1 >= NUMAREAS) {
1304 ::Quit("Area1 out of table range.");
1305 }
1306
1307 area2 = GetAreaNumber(static_cast<int8_t>(tilex - xofs), static_cast<int8_t>(tiley - yofs));
1308 if (area2 >= NUMAREAS) {
1309 ::Quit("Area2 out of table range.");
1310 }
1311
1312 // Connect these two areas.
1313 //
1314 areaconnect[area1][area2] += xform;
1315 areaconnect[area2][area1] += xform;
1316 ConnectAreas();
1317
1318 return area1;
1319 }
1320
DoorOpening(int16_t door)1321 void DoorOpening(
1322 int16_t door)
1323 {
1324 int16_t area1;
1325 int32_t position;
1326
1327 position = doorposition[door];
1328 if (!position) {
1329 area1 = TransformAreas(doorobjlist[door].tilex, doorobjlist[door].tiley, 1);
1330
1331 if (areabyplayer[area1]) {
1332 switch (doorobjlist[door].type) {
1333 case dr_bio:
1334 case dr_office:
1335 case dr_space:
1336 case dr_normal:
1337 ::sd_play_door_sound(
1338 HTECHDOOROPENSND, &doorobjlist[door]);
1339 break;
1340
1341 default:
1342 ::sd_play_door_sound(OPENDOORSND, &doorobjlist[door]);
1343 break;
1344 }
1345 }
1346 }
1347
1348 //
1349 // slide the door by an adaptive amount
1350 //
1351 position += tics << 10;
1352 if (position >= 0xffff) {
1353 //
1354 // door is all the way open
1355 //
1356 position = 0xffff;
1357 doorobjlist[door].ticcount = 0;
1358 doorobjlist[door].action = dr_open;
1359 actorat[doorobjlist[door].tilex][doorobjlist[door].tiley] = 0;
1360 }
1361
1362 doorposition[door] = static_cast<uint16_t>(position);
1363 }
1364
DoorClosing(int16_t door)1365 void DoorClosing(
1366 int16_t door)
1367 {
1368 int32_t position;
1369 int16_t tilex, tiley;
1370
1371 tilex = doorobjlist[door].tilex;
1372 tiley = doorobjlist[door].tiley;
1373
1374 if ((actorat[tilex][tiley] != reinterpret_cast<objtype*>(static_cast<size_t>(door | 0x80))) ||
1375 (player->tilex == tilex && player->tiley == tiley))
1376 {
1377 // something got inside the door
1378 OpenDoor(door);
1379 return;
1380 }
1381
1382 position = doorposition[door];
1383
1384 //
1385 // slide the door by an adaptive amount
1386 //
1387 position -= tics << 10;
1388 if (position <= 0) {
1389 position = 0;
1390 doorobjlist[door].action = dr_closed;
1391 TransformAreas(doorobjlist[door].tilex, doorobjlist[door].tiley, -1);
1392 }
1393
1394 doorposition[door] = static_cast<uint16_t>(position);
1395 }
1396
1397 /*
1398 =====================
1399 =
1400 = MoveDoors
1401 =
1402 = Called from PlayLoop
1403 =
1404 =====================
1405 */
MoveDoors()1406 void MoveDoors()
1407 {
1408 int16_t door;
1409
1410 for (door = 0; door < doornum; door++) {
1411 switch (doorobjlist[door].action) {
1412 case dr_open:
1413 DoorOpen(door);
1414 break;
1415
1416 case dr_opening:
1417 DoorOpening(door);
1418 break;
1419
1420 case dr_closing:
1421 DoorClosing(door);
1422 break;
1423
1424 default:
1425 break;
1426 }
1427 }
1428 }
1429
1430
1431 /*
1432 =============================================================================
1433
1434 PUSHABLE WALLS
1435
1436 =============================================================================
1437 */
1438
1439 uint16_t pwallstate;
1440 uint16_t pwallpos; // amount a pushable wall has been moved (0-63)
1441 uint16_t pwallx = 0, pwally = 0;
1442 int16_t pwalldir, pwalldist;
1443
1444
PushWall(int16_t checkx,int16_t checky,int16_t dir)1445 void PushWall(
1446 int16_t checkx,
1447 int16_t checky,
1448 int16_t dir)
1449 {
1450 int16_t oldtile;
1451
1452 if (pwallstate) {
1453 return;
1454 }
1455
1456 TransformAreas(static_cast<int8_t>(checkx), static_cast<int8_t>(checky), 1);
1457
1458 oldtile = tilemap[checkx][checky];
1459 if (!oldtile) {
1460 return;
1461 }
1462
1463 switch (dir) {
1464 case di_north:
1465 if (actorat[checkx][checky - 1]) {
1466 return;
1467 }
1468
1469 tilemap[checkx][checky - 1] = static_cast<uint8_t>(oldtile);
1470 actorat[checkx][checky - 1] = reinterpret_cast<objtype*>(oldtile);
1471 break;
1472
1473 case di_east:
1474 if (actorat[checkx + 1][checky]) {
1475 return;
1476 }
1477
1478 tilemap[checkx + 1][checky] = static_cast<uint8_t>(oldtile);
1479 actorat[checkx + 1][checky] = reinterpret_cast<objtype*>(oldtile);
1480 break;
1481
1482 case di_south:
1483 if (actorat[checkx][checky + 1]) {
1484 return;
1485 }
1486
1487 tilemap[checkx][checky + 1] = static_cast<uint8_t>(oldtile);
1488 actorat[checkx][checky + 1] = reinterpret_cast<objtype*>(oldtile);
1489 break;
1490
1491 case di_west:
1492 if (actorat[checkx - 1][checky]) {
1493 return;
1494 }
1495
1496 tilemap[checkx - 1][checky] = static_cast<uint8_t>(oldtile);
1497 actorat[checkx - 1][checky] = reinterpret_cast<objtype*>(oldtile);
1498 break;
1499 }
1500
1501 pwalldist = 2;
1502 pwallx = checkx;
1503 pwally = checky;
1504 pwalldir = dir;
1505 pwallstate = 1;
1506 pwallpos = 0;
1507 tilemap[pwallx][pwally] |= 0xc0;
1508 *(mapsegs[1] + farmapylookup[pwally] + pwallx) = 0; // remove P tile info
1509
1510 ::sd_play_wall_sound(PUSHWALLSND);
1511 }
1512
MovePWalls()1513 void MovePWalls()
1514 {
1515 int16_t oldblock, oldtile;
1516
1517 if (!pwallstate) {
1518 return;
1519 }
1520
1521 oldblock = pwallstate / 128;
1522
1523 pwallstate += tics * 4;
1524
1525 if (pwallstate / 128 != oldblock) {
1526 uint16_t areanumber;
1527
1528 pwalldist--;
1529
1530 // block crossed into a new block
1531 oldtile = tilemap[pwallx][pwally] & 63;
1532
1533 //
1534 // the tile can now be walked into
1535 //
1536 tilemap[pwallx][pwally] = 0;
1537
1538 actorat[pwallx][pwally] = nullptr;
1539
1540 areanumber = GetAreaNumber(player->tilex, player->tiley);
1541 if (GAN_HiddenArea) {
1542 areanumber += HIDDENAREATILE;
1543 } else {
1544 areanumber += AREATILE;
1545 }
1546 *(mapsegs[0] + farmapylookup[pwally] + pwallx) = areanumber;
1547
1548 //
1549 // see if it should be pushed farther
1550 //
1551 if (!pwalldist) {
1552 //
1553 // the block has been pushed two tiles
1554 //
1555 pwallstate = 0;
1556 return;
1557 } else {
1558 switch (pwalldir) {
1559 case di_north:
1560 pwally--;
1561 if (actorat[pwallx][pwally - 1]) {
1562 pwallstate = 0;
1563 return;
1564 }
1565
1566 tilemap[pwallx][pwally - 1] = static_cast<uint8_t>(oldtile);
1567 actorat[pwallx][pwally - 1] = reinterpret_cast<objtype*>(oldtile);
1568 break;
1569
1570 case di_east:
1571 pwallx++;
1572 if (actorat[pwallx + 1][pwally]) {
1573 pwallstate = 0;
1574 return;
1575 }
1576
1577 tilemap[pwallx + 1][pwally] = static_cast<uint8_t>(oldtile);
1578 actorat[pwallx + 1][pwally] = reinterpret_cast<objtype*>(oldtile);
1579 break;
1580
1581 case di_south:
1582 pwally++;
1583 if (actorat[pwallx][pwally + 1]) {
1584 pwallstate = 0;
1585 return;
1586 }
1587
1588 tilemap[pwallx][pwally + 1] = static_cast<uint8_t>(oldtile);
1589 actorat[pwallx][pwally + 1] = reinterpret_cast<objtype*>(oldtile);
1590 break;
1591
1592 case di_west:
1593 pwallx--;
1594 if (actorat[pwallx - 1][pwally]) {
1595 pwallstate = 0;
1596 return;
1597 }
1598
1599 tilemap[pwallx - 1][pwally] = static_cast<uint8_t>(oldtile);
1600 actorat[pwallx - 1][pwally] = reinterpret_cast<objtype*>(oldtile);
1601 break;
1602 }
1603
1604 tilemap[pwallx][pwally] = static_cast<uint8_t>(oldtile | 0xc0);
1605 }
1606 }
1607
1608
1609 pwallpos = (pwallstate / 2) & 63;
1610 }
1611
1612 // ==========================================================================
1613 //
1614 // 'SPECIAL MESSAGE' CACHING SYSTEM
1615 //
1616 // When creating special 'types' of message caching structures, make sure
1617 // all 'special data' is placed at the end of the BASIC message structures.
1618 // In memory, BASIC INFO should appear first. ex:
1619 //
1620 // mCacheList
1621 // ---> NumMsgs
1622 // ---> mCacheInfo
1623 // ---> local_val
1624 // ---> global_val
1625 // ---> mSeg
1626 //
1627 // ... all special data follows ...
1628 //
1629 // ==========================================================================
1630
InitMsgCache(mCacheList * mList,uint16_t listSize,uint16_t infoSize)1631 void InitMsgCache(
1632 mCacheList* mList,
1633 uint16_t listSize,
1634 uint16_t infoSize)
1635 {
1636 FreeMsgCache(mList, infoSize);
1637 memset(mList, 0, listSize);
1638 }
1639
FreeMsgCache(mCacheList * mList,uint16_t infoSize)1640 void FreeMsgCache(
1641 mCacheList* mList,
1642 uint16_t infoSize)
1643 {
1644 mCacheInfo* ci = mList->mInfo;
1645 char* ch_ptr;
1646
1647 while (mList->NumMsgs--) {
1648 delete [] ci->mSeg;
1649 ci->mSeg = nullptr;
1650
1651 ch_ptr = (char*)ci;
1652 ch_ptr += infoSize;
1653 ci = (mCacheInfo*)ch_ptr;
1654 }
1655 }
1656
1657 extern char int_xx[];
1658
1659 // ---------------------------------------------------------------------------
1660 // CacheMsg()
1661 //
1662 // Caches the specific message in FROM a given 'grsegs' block TO the
1663 // next available message segment pointer.
1664 // ---------------------------------------------------------------------------
CacheMsg(mCacheInfo * ci,uint16_t SegNum,uint16_t MsgNum)1665 void CacheMsg(
1666 mCacheInfo* ci,
1667 uint16_t SegNum,
1668 uint16_t MsgNum)
1669 {
1670 ci->mSeg = new char[MAX_CACHE_MSG_LEN];
1671 LoadMsg(ci->mSeg, SegNum, MsgNum, MAX_CACHE_MSG_LEN);
1672 }
1673
1674 // ---------------------------------------------------------------------------
1675 // LoadMsg()
1676 //
1677 // Loads the specific message in FROM a given 'grsegs' block TO the
1678 // the memory address provided. Memory allocation and handleing prior and
1679 // after this function usage is responsiblity of the calling function(s).
1680 //
1681 // PARAMS: hint_buffer - Destination address to store message
1682 // SegNum - GrSeg for messages in VGAGRAPH.BS?
1683 // MsgNum - Message number to load
1684 // MaxMsgLen - Max len of cache msg (Len of hint_buffer)
1685 //
1686 // RETURNS : Returns the length of the loaded message
1687 // ---------------------------------------------------------------------------
LoadMsg(char * hint_buffer,uint16_t SegNum,uint16_t MsgNum,uint16_t MaxMsgLen)1688 int16_t LoadMsg(
1689 char* hint_buffer,
1690 uint16_t SegNum,
1691 uint16_t MsgNum,
1692 uint16_t MaxMsgLen)
1693 {
1694 char* Message, * EndOfMsg;
1695 int16_t pos = 0;
1696
1697 CA_CacheGrChunk(SegNum);
1698 Message = static_cast<char*>(grsegs[SegNum]);
1699
1700 // Search for end of MsgNum-1 (Start of our message)
1701 //
1702 while (--MsgNum) {
1703 Message = strstr(Message, int_xx);
1704
1705 if (!Message) {
1706 ::Quit("Invalid 'Cached Message' number");
1707 }
1708
1709 Message += 3; // Bump to start of next Message
1710 }
1711
1712 // Move past LFs and CRs that follow "^XX"
1713 //
1714 while ((*Message == '\n') || (*Message == '\r')) {
1715 Message++;
1716 }
1717
1718 // Find the end of the message
1719 //
1720 if ((EndOfMsg = strstr(Message, int_xx)) == nullptr) {
1721 ::Quit("Invalid 'Cached Message' number");
1722 }
1723 EndOfMsg += 3;
1724
1725 // Copy to a temp buffer
1726 //
1727 while (Message != EndOfMsg) {
1728 if (*Message != '\n') {
1729 hint_buffer[pos++] = *Message;
1730 }
1731
1732 if (pos >= MaxMsgLen) {
1733 ::Quit("Cached Hint Message is to Long for allocated space.");
1734 }
1735
1736 Message++;
1737 }
1738
1739 hint_buffer[pos] = 0; // Null Terminate
1740 UNCACHEGRCHUNK(SegNum);
1741
1742 return pos;
1743 }
1744
1745
1746 /*
1747 =============================================================================
1748
1749 CONCESSION MACHINES
1750
1751 =============================================================================
1752 */
1753
1754 // --------------------------------------------------------------------------
1755 // SpawnConcession()
1756 //
1757 // actorat[][] - Holds concession machine number (1 - MAXCONCESSIONS+1)
1758 // --------------------------------------------------------------------------
SpawnConcession(int16_t tilex,int16_t tiley,uint16_t credits,uint16_t machinetype)1759 void SpawnConcession(
1760 int16_t tilex,
1761 int16_t tiley,
1762 uint16_t credits,
1763 uint16_t machinetype)
1764 {
1765 con_mCacheInfo* ci = &ConHintList.cmInfo[ConHintList.NumMsgs];
1766
1767 if (ConHintList.NumMsgs >= MAXCONCESSIONS) {
1768 ::Quit("Too many concession machines in level.");
1769 }
1770
1771 if (credits != PUSHABLETILE) {
1772 switch (credits & 0xff00) {
1773 case 0:
1774 case 0xFC00: // Food Id
1775 ci->mInfo.local_val = credits & 0xff;
1776 ci->operate_cnt = 0;
1777 ci->type = static_cast<uint8_t>(machinetype);
1778 break;
1779 }
1780 }
1781
1782 // Consider it a solid wall (val != 0)
1783 //
1784 if (++ConHintList.NumMsgs > MAX_CACHE_MSGS) {
1785 ::Quit("(CONCESSIONS) Too many 'cached msgs' loaded.");
1786 }
1787
1788 actorat[static_cast<int>(tilex)][static_cast<int>(tiley)] = reinterpret_cast<objtype*>(ConHintList.NumMsgs);
1789 }
1790
ReuseMsg(mCacheInfo * ci,int16_t count,int16_t struct_size)1791 bool ReuseMsg(
1792 mCacheInfo* ci,
1793 int16_t count,
1794 int16_t struct_size)
1795 {
1796 char* scan_ch = (char*)ci;
1797 mCacheInfo* scan = (mCacheInfo*)(scan_ch - struct_size);
1798
1799 // Scan through all loaded messages -- see if we're loading one already
1800 // cached-in.
1801 //
1802 while (count--) {
1803 // Is this message already cached in?
1804 //
1805 if (scan->global_val == ci->global_val) {
1806 ci->local_val = scan->local_val;
1807 return true;
1808 }
1809
1810 // Funky structure decrement... (structures can be any size...)
1811 //
1812 scan_ch = (char*)scan;
1813 scan_ch -= struct_size;
1814 scan = (mCacheInfo*)scan_ch;
1815 }
1816
1817 return false;
1818 }
1819
1820
1821 extern std::string food_msg1;
1822 extern std::string bevs_msg1;
1823
1824 extern void writeTokenStr(
1825 std::string& string);
1826
1827 const char* const OutOrder = "\r\r FOOD UNIT MACHINE\r IS OUT OF ORDER.^XX";
1828
OperateConcession(uint16_t concession)1829 void OperateConcession(
1830 uint16_t concession)
1831 {
1832 con_mCacheInfo* ci;
1833 bool ok = false;
1834
1835 ci = &ConHintList.cmInfo[concession - 1];
1836
1837 switch (ci->type) {
1838 case CT_FOOD:
1839 case CT_BEVS:
1840 if (ci->mInfo.local_val) {
1841 if (gamestate.health == 100) {
1842 DISPLAY_TIMED_MSG(noeat_msg1, MP_CONCESSION_OPERATE, MT_GENERAL);
1843 ::sd_play_player_sound(NOWAYSND, bstone::AC_ITEM);
1844
1845 return;
1846 } else {
1847 ok = true;
1848 }
1849 }
1850 break;
1851 }
1852
1853 if (ok) {
1854 // Whada' ya' want?
1855
1856 switch (ci->type) {
1857 case CT_FOOD:
1858 case CT_BEVS:
1859 // One token please... Thank you.
1860
1861 if (!gamestate.tokens) {
1862 DISPLAY_TIMED_MSG(NoFoodTokens, MP_NO_MORE_TOKENS, MT_NO_MO_FOOD_TOKENS);
1863 ::sd_play_player_sound(NOWAYSND, bstone::AC_ITEM);
1864
1865 return;
1866 } else {
1867 gamestate.tokens--;
1868 }
1869
1870 ci->mInfo.local_val--;
1871 ::sd_play_player_sound(CONCESSIONSSND, bstone::AC_ITEM);
1872
1873 switch (ci->type) {
1874 case CT_FOOD:
1875 if (::is_ps()) {
1876 ::writeTokenStr(food_msg1);
1877 }
1878
1879 DISPLAY_TIMED_MSG(food_msg1, MP_CONCESSION_OPERATE, MT_GENERAL);
1880 HealSelf(10);
1881 break;
1882
1883 case CT_BEVS:
1884 if (::is_ps()) {
1885 ::writeTokenStr(bevs_msg1);
1886 }
1887
1888 DISPLAY_TIMED_MSG(bevs_msg1, MP_CONCESSION_OPERATE, MT_GENERAL);
1889 HealSelf(7);
1890 break;
1891 }
1892 break;
1893 }
1894 } else {
1895 DISPLAY_TIMED_MSG(OutOrder, MP_CONCESSION_OUT_ORDER, MT_GENERAL);
1896 ::sd_play_player_sound(NOWAYSND, bstone::AC_ITEM);
1897 }
1898 }
1899
1900 int8_t xy_offset[8][2] = {
1901 { 0, -1 }, { 0, +1 }, { -1, 0 }, { +1, 0 }, // vert / horz
1902 { -1, -1 }, { +1, +1 }, { -1, +1 }, { +1, -1 }, // diagnals
1903 };
1904
CheckSpawnEA()1905 void CheckSpawnEA()
1906 {
1907 objtype temp, * ob;
1908 int8_t loop, ofs, x_diff, y_diff;
1909
1910 if (objcount > MAXACTORS - 8) {
1911 return;
1912 }
1913
1914 for (loop = 0; loop < NumEAWalls; loop++) {
1915 uint16_t* map1 = mapsegs[1] + farmapylookup[static_cast<int>(eaList[static_cast<int>(loop)].tiley)] + eaList[static_cast<int>(loop)].tilex;
1916
1917 // Limit the number of aliens spawned by each outlet.
1918 //
1919 if (eaList[static_cast<int>(loop)].aliens_out > gamestate.difficulty) {
1920 continue;
1921 }
1922
1923 // Decrement 'spawn delay' for current outlet.
1924 //
1925 if (eaList[static_cast<int>(loop)].delay > tics) {
1926 eaList[static_cast<int>(loop)].delay -= tics;
1927 continue;
1928 }
1929
1930 // Reset to 1 because it's possible that an alien won't be spawned...
1931 // If NOT, we'll try again on the next refresh.
1932 // If SO, the delay is set to a true value below.
1933 //
1934 eaList[static_cast<int>(loop)].delay = 1;
1935
1936 // Does this wall touch the 'area' that the player is in?
1937 //
1938 for (ofs = 0; ofs < 4; ofs++) {
1939 int8_t nx = eaList[static_cast<int>(loop)].tilex + xy_offset[static_cast<int>(ofs)][0];
1940 int8_t ny = eaList[static_cast<int>(loop)].tiley + xy_offset[static_cast<int>(ofs)][1];
1941 int8_t areanumber = GetAreaNumber(nx, ny);
1942
1943 if ((nx < 0) || (nx > 63) || (ny < 0) || (ny > 63)) {
1944 continue;
1945 }
1946
1947 if (areanumber != 127 && areabyplayer[static_cast<int>(areanumber)]) {
1948 break;
1949 }
1950 }
1951
1952 // Wall doesn't touch player 'area'.
1953 //
1954 if (ofs == 4) {
1955 continue;
1956 }
1957
1958 // Setup tile x,y in temp obj.
1959 //
1960 temp.tilex = eaList[static_cast<int>(loop)].tilex + xy_offset[static_cast<int>(ofs)][0];
1961 temp.tiley = eaList[static_cast<int>(loop)].tiley + xy_offset[static_cast<int>(ofs)][1];
1962
1963 // Is another actor already on this tile?
1964 // If so, "continue" if it's alive...
1965 //
1966 ob = actorat[temp.tilex][temp.tiley];
1967 if (ob >= objlist) {
1968 if (!(ob->flags & FL_DEADGUY)) {
1969 continue;
1970 }
1971 }
1972
1973 // Is player already on this tile?
1974 //
1975 x_diff = player->tilex - temp.tilex;
1976 y_diff = player->tiley - temp.tiley;
1977 if (ABS(x_diff) < 2 && ABS(y_diff) < 2) {
1978 continue;
1979 }
1980
1981 // Setup x,y in temp obj and see if obj is in player's view.
1982 // Actor is released if it's in player's view OR
1983 // a random chance to release whether it can be seen or not.
1984 //
1985 temp.x = ((fixed)temp.tilex << TILESHIFT) + ((fixed)TILEGLOBAL / 2);
1986 temp.y = ((fixed)temp.tiley << TILESHIFT) + ((fixed)TILEGLOBAL / 2);
1987 if ((!CheckSight(player, &temp)) && (US_RndT() < 200)) {
1988 continue;
1989 }
1990
1991 // Spawn Electro-Alien!
1992 //
1993 usedummy = true;
1994 SpawnStand(en_electro_alien, temp.tilex, temp.tiley, 0);
1995
1996 ::sd_play_actor_sound(ELECAPPEARSND, new_actor, bstone::AC_ITEM);
1997
1998 usedummy = false;
1999 if (new_actor != &dummyobj) {
2000 eaList[static_cast<int>(loop)].aliens_out++;
2001 new_actor->temp2 = loop;
2002 ::sd_play_actor_sound(ELECAPPEARSND, new_actor, bstone::AC_ITEM);
2003 }
2004
2005 // Reset spawn delay.
2006 //
2007 if ((*map1 & 0xff00) == 0xfa00) {
2008 eaList[static_cast<int>(loop)].delay = 60 * ((*map1) & 0xff);
2009 } else {
2010 eaList[static_cast<int>(loop)].delay = 60 * 8 + Random(60 * 22);
2011 }
2012
2013 break;
2014 }
2015 }
2016
CheckSpawnGoldstern()2017 void CheckSpawnGoldstern()
2018 {
2019 if (GoldsternInfo.WaitTime > tics) {
2020 //
2021 // Count down general timer before doing any Goldie Stuff..
2022 //
2023
2024 GoldsternInfo.WaitTime -= tics;
2025 } else {
2026 //
2027 // What Kind of Goldie Stuff needs to be done?
2028 //
2029
2030 if (GoldsternInfo.flags == GS_COORDFOUND) {
2031 uint16_t tilex, tiley;
2032
2033 // See if we can spawn Dr. Goldstern...
2034
2035 tilex = GoldieList[GoldsternInfo.LastIndex].tilex;
2036 tiley = GoldieList[GoldsternInfo.LastIndex].tiley;
2037
2038 if ((!actorat[tilex][tiley]) && ABS(player->tilex - tilex) > 1 && ABS(player->tiley - tiley) > 1) {
2039 SpawnStand(en_goldstern, tilex, tiley, 0);
2040 GoldsternInfo.GoldSpawned = true;
2041 }
2042 } else {
2043 // Find a new coord to spawn Goldie (GS_NEEDCOORD or GS_FIRSTTIME)
2044
2045 FindNewGoldieSpawnSite();
2046 }
2047 }
2048 }
2049
FindNewGoldieSpawnSite()2050 void FindNewGoldieSpawnSite()
2051 {
2052 objtype temp;
2053 int8_t loop;
2054
2055 GoldsternInfo.WaitTime = 0;
2056
2057 for (loop = 0; loop < GoldsternInfo.SpawnCnt; loop++) {
2058 // Test for repeats - And avoid them!
2059 //
2060
2061 if ((GoldsternInfo.SpawnCnt > 1) && (loop == GoldsternInfo.LastIndex)) {
2062 continue;
2063 }
2064
2065 // Setup tile x,y in temp obj.
2066 //
2067
2068 temp.tilex = GoldieList[static_cast<int>(loop)].tilex;
2069 temp.tiley = GoldieList[static_cast<int>(loop)].tiley;
2070
2071 // Setup x,y in temp obj and see if obj is in player's view.
2072 //
2073
2074 temp.x = ((fixed)temp.tilex << TILESHIFT) + ((fixed)TILEGLOBAL / 2);
2075 temp.y = ((fixed)temp.tiley << TILESHIFT) + ((fixed)TILEGLOBAL / 2);
2076 if (!CheckSight(player, &temp)) {
2077 continue;
2078 }
2079
2080 // Mark to spawn Dr Goldstern
2081 //
2082
2083 GoldsternInfo.LastIndex = loop;
2084 if (gamestate.mapon == 9) {
2085 GoldsternInfo.WaitTime = 60;
2086 } else if (GoldsternInfo.flags == GS_FIRSTTIME) {
2087 GoldsternInfo.WaitTime = MIN_GOLDIE_FIRST_WAIT + Random(MAX_GOLDIE_FIRST_WAIT - MIN_GOLDIE_FIRST_WAIT); // Reinit Delay Timer before spawning on new position
2088 } else {
2089 GoldsternInfo.WaitTime = MIN_GOLDIE_WAIT + Random(MAX_GOLDIE_WAIT - MIN_GOLDIE_WAIT); // Reinit Delay Timer before spawning on new position
2090
2091 }
2092 GoldsternInfo.flags = GS_COORDFOUND;
2093 break;
2094 }
2095 }
2096