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 <deque>
26 #include <array>
27 #include "3d_def.h"
28 #include "bstone_ref_values.h"
29
30
31 void InitWeaponBounce();
32 void HandleWeaponBounce();
33
34 void VL_LatchToScreen(
35 int source,
36 int width,
37 int height,
38 int x,
39 int y);
40
41 void StartDamageFlash(
42 int damage);
43
44 void StartBonusFlash();
45
46 int16_t CalcAngle(
47 objtype* from_obj,
48 objtype* to_obj);
49
50 void PushWall(
51 int16_t checkx,
52 int16_t checky,
53 int16_t dir);
54
55 void OperateDoor(
56 int16_t door);
57
58 void TryDropPlasmaDetonator();
59
60 void ClearMemory();
61 void VH_UpdateScreen();
62 void InitAreas();
63
64 void FirstSighting(
65 objtype* ob);
66
67 void OpenDoor(
68 int16_t door);
69
70 void DrawTopInfo(
71 sp_type type);
72
73 void DoActor(
74 objtype* ob);
75
76 void RunBlakeRun();
77
78 #define VW_UpdateScreen() VH_UpdateScreen()
79
80 int16_t DrawShape(
81 int16_t x,
82 int16_t y,
83 int16_t shapenum,
84 pisType shapetype);
85
86
87 /*
88 =============================================================================
89
90 LOCAL CONSTANTS
91
92 =============================================================================
93 */
94
95
96 #define MAXMOUSETURN (10)
97
98 #define MOVESCALE (150L)
99 #define BACKMOVESCALE (100L)
100 #define ANGLESCALE (20)
101 #define MAX_DA (100)
102
103 #define MAX_TERM_COMMAND_LEN (31)
104
105 // Max Score displayable
106
107 #define MAX_DISPLAY_SCORE (9999999L)
108 #define SCORE_ROLL_WAIT (60 * 10) // Tics
109
110 // ECG scroll rate (delay).
111 #define HEALTH_SCROLL_RATE (7)
112 #define HEALTH_PULSE (70)
113
114 // Text "InfoArea" defines
115 #define INFOAREA_X (3)
116 #define INFOAREA_Y (static_cast<uint16_t>(200) - STATUSLINES + 3)
117 #define INFOAREA_W (109)
118 #define INFOAREA_H (37)
119
120 #define INFOAREA_BCOLOR (0x01)
121 #define INFOAREA_CCOLOR (0x1A)
122 #define INFOAREA_TCOLOR (0xA6)
123 #define INFOAREA_TSHAD_COLOR (0x04) // Text Shadow Color
124
125 #define GRENADE_ENERGY_USE (4)
126 #define BFG_ENERGY_USE (GRENADE_ENERGY_USE << 1)
127
128 #define NUM_AMMO_SEGS (21)
129
130 #define AMMO_SMALL_FONT_NUM_WIDTH (5)
131
132
133 /*
134 =============================================================================
135
136 GLOBAL VARIABLES
137
138 =============================================================================
139 */
140
141 extern bool noShots;
142 extern int16_t bounceOk;
143
144 int16_t tryDetonatorDelay = 0;
145
146 //
147 // player state info
148 //
149 int32_t thrustspeed;
150
151 // unsigned plux,pluy; // player coordinates scaled to unsigned
152
153 int anglefrac;
154
155 objtype* LastAttacker;
156
157 bool PlayerInvisable = false;
158
159 char LocationText[MAX_LOCATION_DESC_LEN];
160
161 uint16_t player_oldtilex;
162 uint16_t player_oldtiley;
163
164 // BBi
165 extern bstone::MemoryStream g_playtemp;
166
167
168 /*
169 =============================================================================
170
171 LOCAL VARIABLES
172
173 =============================================================================
174 */
175
176 void writeTokenStr(
177 std::string& string);
178
179 void ShowOverheadChunk();
180
181 void LoadOverheadChunk(
182 int tpNum);
183
184 void SaveOverheadChunk(
185 int tpNum);
186
187 void DisplayTeleportName(
188 int8_t tpNum,
189 bool locked);
190
191 void ForceUpdateStatusBar();
192 void UpdateRadarGuage();
193
194 void DrawLedStrip(
195 int16_t x,
196 int16_t y,
197 int16_t frac,
198 int16_t max);
199
200 void DisplayPinballBonus();
201
202 void CheckPinballBonus(
203 int32_t points);
204
205 uint8_t LevelCompleted();
206
207 void T_Player(
208 objtype* ob);
209
210 void T_Attack(
211 objtype* ob);
212
213 statetype s_player = { 0, 0, 0, &T_Player, nullptr, nullptr };
214 statetype s_attack = { 0, 0, 0, &T_Attack, nullptr, nullptr };
215
216 int32_t playerxmove, playerymove;
217
218 atkinf_t attackinfo[7][14] = {
219 { { 6, 0, 1 }, { 6, 2, 2 }, { 6, 0, 3 }, { 6, -1, 4 } }, // Auto charge
220 { { 6, 0, 1 }, { 6, 1, 2 }, { 6, 0, 3 }, { 6, -1, 4 } }, // Pistol
221 { { 6, 0, 1 }, { 6, 1, 2 }, { 5, 3, 3 }, { 5, -1, 4 } }, // Pulse
222 { { 6, 0, 1 }, { 6, 1, 2 }, { 3, 4, 3 }, { 3, -1, 4 } }, // ION
223 { { 6, 0, 1 }, { 6, 5, 2 }, { 6, 6, 3 }, { 6, -1, 4 } },
224 { { 6, 0, 1 }, { 6, 9, 2 }, { 6, 10, 3 }, { 6, -1, 4 } },
225 { { 5, 7, 0 }, { 5, 8, 0 }, { 2, -2, 0 }, { 0, 0, 0 } },
226 };
227
228
229 #define GD0 (0x55)
230 #define YD0 (0x35)
231 #define RD0 (0x15)
232
233 #define GD1 (0x53)
234 #define YD1 (0x33)
235 #define RD1 (0x13)
236
237 int8_t DimAmmo[2][22] = {
238 { GD0, GD0, GD0, GD0, GD0, GD0, GD0, YD0, YD0, YD0, YD0, YD0, YD0, YD0, RD0, RD0, RD0, RD0, RD0, RD0, RD0, RD0 },
239 { GD1, GD1, GD1, GD1, GD1, GD1, GD1, YD1, YD1, YD1, YD1, YD1, YD1, YD1, RD1, RD1, RD1, RD1, RD1, RD1, RD1, RD1 },
240 };
241
242 #define GL0 (0x58)
243 #define YL0 (0x38)
244 #define RL0 (0x18)
245
246 #define GL1 (0x56)
247 #define YL1 (0x36)
248 #define RL1 (0x16)
249
250 int8_t LitAmmo[2][22] = {
251 { GL0, GL0, GL0, GL0, GL0, GL0, GL0, YL0, YL0, YL0, YL0, YL0, YL0, YL0, RL0, RL0, RL0, RL0, RL0, RL0, RL0, RL0 },
252 { GL1, GL1, GL1, GL1, GL1, GL1, GL1, YL1, YL1, YL1, YL1, YL1, YL1, YL1, RL1, RL1, RL1, RL1, RL1, RL1, RL1, RL1 },
253 };
254
255 #define IA_MAX_LINE (30)
256
257 struct InfoArea_Struct {
258 int16_t x, y;
259 int16_t text_color;
260 int16_t backgr_color;
261 int16_t left_margin;
262 int8_t delay;
263 int8_t numanims;
264 int8_t framecount;
265 }; // InfoArea_Struct
266
267 uint16_t LastMsgPri = 0;
268 int16_t MsgTicsRemain = 0;
269 classtype LastInfoAttacker = nothing;
270
271 int16_t LastInfoAttacker_Cloaked = 0;
272
273 infomsg_type LastMsgType = MT_NOTHING;
274 InfoArea_Struct InfoAreaSetup;
275
276 int8_t DrawRadarGuage_COUNT = 3;
277 int8_t DrawAmmoNum_COUNT = 3;
278 int8_t DrawAmmoPic_COUNT = 3;
279 int8_t DrawScoreNum_COUNT = 3;
280 int8_t DrawWeaponPic_COUNT = 3;
281 int8_t DrawKeyPics_COUNT = 3;
282 int8_t DrawHealthNum_COUNT = 3;
283
284 int8_t DrawInfoArea_COUNT = 3;
285 int8_t InitInfoArea_COUNT = 3;
286 int8_t ClearInfoArea_COUNT = 3;
287
288 void DrawWeapon();
289
290 void GiveWeapon(
291 int weapon);
292
293 void GiveAmmo(
294 int16_t ammo);
295
296 void DrawGAmmoNum();
297 void DrawMAmmoNum();
298 void DrawPDAmmoMsg();
299 void ComputeAvailWeapons();
300
301 void SW_HandleActor(
302 objtype* obj);
303
304 void SW_HandleStatic(
305 statobj_t* stat,
306 uint16_t tilex,
307 uint16_t tiley);
308
309 // ===========================================================================
310
311 uint8_t ShowRatio(
312 int16_t bx,
313 int16_t by,
314 int16_t px,
315 int16_t py,
316 int32_t total,
317 int32_t perc,
318 ss_type type);
319
320 void Attack();
321 void Use();
322
323 void Search(
324 objtype* ob);
325
326 void SelectWeapon();
327 void SelectItem();
328
329 void SpawnPlayer(
330 int16_t tilex,
331 int16_t tiley,
332 int16_t dir);
333
334 void Thrust(
335 int16_t angle,
336 int32_t speed);
337
338 bool TryMove(
339 objtype* ob);
340
341 void T_Player(
342 objtype* ob);
343
344 bool ClipMove(
345 objtype* ob,
346 int32_t xmove,
347 int32_t ymove);
348
349 void SocketToggle(
350 bool TurnOn);
351
352 void CheckStatsBonus();
353
354 void T_Stand(
355 objtype* ob);
356
357
358 /*
359 =============================================================================
360
361 CONTROL STUFF
362
363 =============================================================================
364 */
365
366 /*
367 ======================
368 =
369 = CheckWeaponChange
370 =
371 = Keys 1-6 change weapons
372 =
373 =
374 ======================
375 */
CheckWeaponChange()376 void CheckWeaponChange()
377 {
378 const int n = ::is_ps() ? wp_bfg_cannon : wp_grenade;
379
380 for (int i = wp_autocharge; i <= n; i++) {
381 if (buttonstate[bt_ready_autocharge + i - wp_autocharge]) {
382 if (gamestate.useable_weapons & (1 << i)) {
383 gamestate.weapon = static_cast<int8_t>(i);
384 gamestate.chosenweapon = static_cast<int8_t>(i);
385
386 DISPLAY_TIMED_MSG(WeaponAvailMsg, MP_WEAPON_AVAIL, MT_GENERAL);
387 DrawWeapon();
388 return;
389 } else {
390 DISPLAY_TIMED_MSG(WeaponNotAvailMsg, MP_WEAPON_AVAIL, MT_GENERAL);
391 }
392 }
393 }
394 }
395
396 /*
397 =======================
398 =
399 = ControlMovement
400 =
401 = Takes controlx,controly, and buttonstate[bt_strafe]
402 =
403 = Changes the player's angle and position
404 =
405 = There is an angle hack because when going 70 fps, the roundoff becomes
406 = significant
407 =
408 =======================
409 */
ControlMovement(objtype * ob)410 void ControlMovement(
411 objtype* ob)
412 {
413 bool use_classic_strafe =
414 (in_use_modern_bindings && in_is_binding_pressed(e_bi_strafe)) ||
415 (!in_use_modern_bindings && buttonstate[bt_strafe]);
416
417 bool use_modern_strafe = false;
418
419 thrustspeed = 0;
420
421 int oldx = player->x;
422 int oldy = player->y;
423
424 //
425 // side to side move
426 //
427
428 if (use_classic_strafe) {
429 if (in_use_modern_bindings) {
430 use_modern_strafe = true;
431 } else {
432 if (controlx > 0) {
433 int angle = ob->angle - ANGLES / 4;
434 if (angle < 0) {
435 angle += ANGLES;
436 }
437 Thrust(static_cast<int16_t>(angle), controlx * MOVESCALE); // move to left
438 } else if (controlx < 0) {
439 int angle = ob->angle + ANGLES / 4;
440 if (angle >= ANGLES) {
441 angle -= ANGLES;
442 }
443 Thrust(static_cast<int16_t>(angle), -controlx * MOVESCALE); // move to right
444 }
445 }
446 } else if (!gamestate.turn_around) {
447 use_modern_strafe = true;
448 }
449
450 if (use_modern_strafe && strafe_value != 0) {
451 int sign = (strafe_value > 0) ? 1 : -1;
452 int angle = ob->angle + (sign * (ANGLES / 4));
453
454 if (angle < 0) {
455 angle += ANGLES;
456 } else if (angle >= ANGLES) {
457 angle -= ANGLES;
458 }
459
460 Thrust(static_cast<int16_t>(angle), -abs(strafe_value) * MOVESCALE);
461 }
462
463 if (!use_classic_strafe)
464 {
465 if (::gamestate.turn_around)
466 {
467 ::controlx = 100 * tics;
468
469 if (::gamestate.turn_around < 0)
470 {
471 ::controlx = -::controlx;
472 }
473 }
474
475 //
476 // not strafing
477 //
478 anglefrac = anglefrac + controlx;
479 int angleunits = anglefrac / ANGLESCALE;
480 anglefrac -= angleunits * ANGLESCALE;
481 ob->angle -= angleunits;
482
483 while (ob->angle >= ANGLES)
484 {
485 ob->angle -= ANGLES;
486 }
487
488 while (ob->angle < 0)
489 {
490 ob->angle += ANGLES;
491 }
492
493 if (gamestate.turn_around)
494 {
495 bool done = false;
496
497 if (gamestate.turn_around > 0)
498 {
499 gamestate.turn_around = static_cast<int16_t>(gamestate.turn_around - angleunits);
500
501 if (gamestate.turn_around <= 0)
502 {
503 done = true;
504 }
505 }
506 else
507 {
508 gamestate.turn_around = static_cast<int16_t>(gamestate.turn_around - angleunits);
509
510 if (gamestate.turn_around >= 0)
511 {
512 done = true;
513 }
514 }
515
516 if (done)
517 {
518 gamestate.turn_around = 0;
519 ob->angle = gamestate.turn_angle;
520 }
521 }
522 }
523
524
525 //
526 // forward/backwards move
527 //
528 if (controly < 0) {
529 Thrust(ob->angle, -controly * MOVESCALE); // move forwards
530 } else if (controly > 0) {
531 int angle = ob->angle + ANGLES / 2;
532 if (angle >= ANGLES) {
533 angle -= ANGLES;
534 }
535 Thrust(static_cast<int16_t>(angle), controly * BACKMOVESCALE); // move backwards
536 } else if (bounceOk) {
537 bounceOk--;
538 }
539
540 if (controly) {
541 bounceOk = 8;
542 } else if (bounceOk) {
543 bounceOk--;
544 }
545
546 ob->dir = static_cast<dirtype>(((ob->angle + 22) % 360) / 45);
547
548 //
549 // calculate total move
550 //
551 playerxmove = player->x - oldx;
552 playerymove = player->y - oldy;
553 }
554
555
556 /*
557 =============================================================================
558
559 STATUS WINDOW STUFF
560
561 =============================================================================
562 */
563
STATUSDRAWPIC(int x,int y,int picnum)564 static void STATUSDRAWPIC(
565 int x,
566 int y,
567 int picnum)
568 {
569 ::LatchDrawPic(x, y + (200 - STATUSLINES), picnum);
570 }
571
StatusAllDrawPic(uint16_t x,uint16_t y,uint16_t picnum)572 void StatusAllDrawPic(
573 uint16_t x,
574 uint16_t y,
575 uint16_t picnum)
576 {
577 ::STATUSDRAWPIC(x, y, picnum);
578 }
579
580 /*
581 ===============
582 =
583 = LatchNumber
584 =
585 = right justifies and pads with blanks
586 =
587 ===============
588 */
LatchNumber(int16_t x,int16_t y,int16_t width,int32_t number)589 void LatchNumber(
590 int16_t x,
591 int16_t y,
592 int16_t width,
593 int32_t number)
594 {
595 auto wide = 0;
596 auto number_string = std::to_string(number);
597 auto length = static_cast<int>(number_string.length());
598
599 while (length < width && wide < width) {
600 STATUSDRAWPIC(x, y, N_BLANKPIC);
601 ++x;
602 ++wide;
603 ++length;
604 }
605
606 auto c = 0;
607
608 while (wide < width) {
609 STATUSDRAWPIC(x, y, number_string[c] - '0' + N_0PIC);
610 ++x;
611 ++c;
612 ++wide;
613 }
614 }
615
616
617 // ===========================================================================
618 //
619 //
620 // SCORE DISPLAY ROUTINES
621 //
622 //
623 // ===========================================================================
624
625 namespace {
626
627
628 int ecg_scroll_tics = 0;
629 int ecg_next_scroll_tics = 0;
630 std::deque<int> ecg_legend(6);
631 std::deque<int> ecg_segments(6);
632
633 int heart_picture_index = ECG_GRID_PIECE;
634 int heart_sign_tics = 0;
635 int heart_sign_next_tics = 0;
636
637
638 } // namespace
639
640
641 // Draws electrocardiogram (ECG) and the heart sign
DrawHealthMonitor()642 void DrawHealthMonitor()
643 {
644 //
645 // ECG
646 //
647
648 // ECG segment indices:
649 // 0 - silence
650 // 1..8 - shape #1 (health 66%-100%)
651 // 9..17 - shape #2 (health 33%-65%)
652 // 18..27 - shape #3 (health 0%-32%)
653
654 // ECG segment legend:
655 // 0 - silence
656 // 1 - signal #1 (health 66%-100%)
657 // 2 - signal #2 (health 33%-65%)
658 // 3 - signal #3 (health 0%-32%)
659
660 if (ecg_scroll_tics >= ecg_next_scroll_tics) {
661 ecg_scroll_tics = 0;
662 ecg_next_scroll_tics = HEALTH_SCROLL_RATE;
663
664 bool carry = false;
665
666 for (int i = 5; i >= 0; --i) {
667 if (carry) {
668 carry = false;
669 ecg_legend[i] = ecg_legend[i + 1];
670 ecg_segments[i] = ecg_segments[i + 1] - 4;
671 } else if (ecg_segments[i] != 0) {
672 ecg_segments[i] += 1;
673
674 bool use_carry = false;
675
676 if (ecg_legend[i] == 1 && ecg_segments[i] == 5) {
677 use_carry = true;
678 } else if (ecg_legend[i] == 2 && ecg_segments[i] == 13) {
679 use_carry = true;
680 }
681 if (ecg_legend[i] == 3 &&
682 (ecg_segments[i] == 22 || ecg_segments[i] == 27))
683 {
684 use_carry = true;
685 }
686
687 if (use_carry) {
688 carry = true;
689 } else {
690 bool skip = false;
691
692 if (ecg_legend[i] == 1 && ecg_segments[i] > 8) {
693 skip = true;
694 } else if (ecg_legend[i] == 2 && ecg_segments[i] > 17) {
695 skip = true;
696 }
697 if (ecg_legend[i] == 3 && ecg_segments[i] > 27) {
698 skip = true;
699 }
700
701 if (skip) {
702 ecg_legend[i] = 0;
703 ecg_segments[i] = 0;
704 }
705 }
706 }
707 }
708
709 if (gamestate.health > 0 && ecg_legend[5] == 0) {
710 if (gamestate.health < 33) {
711 ecg_legend[5] = 3;
712 ecg_segments[5] = 18;
713 } else if (gamestate.health >= 66) {
714 if (ecg_legend[4] == 0 || ecg_legend[4] != 1) {
715 ecg_legend[5] = 1;
716 ecg_segments[5] = 1;
717 }
718 } else {
719 ecg_legend[5] = 2;
720 ecg_segments[5] = 9;
721 }
722 }
723 } else {
724 ecg_scroll_tics += tics;
725 }
726
727 for (int i = 0; i < 6; ++i) {
728 ::CA_CacheGrChunk(
729 static_cast<int16_t>(ECG_HEARTBEAT_00 + ecg_segments[i]));
730
731 ::VWB_DrawPic(
732 120 + (i * 8),
733 200 - STATUSLINES + 8,
734 ECG_HEARTBEAT_00 + ecg_segments[i]);
735 }
736
737
738 //
739 // Heart sign
740 //
741
742 bool reset_heart_tics = false;
743
744 if (gamestate.health <= 0) {
745 reset_heart_tics = true;
746 heart_picture_index = ECG_GRID_PIECE;
747 } else if (gamestate.health < 40) {
748 reset_heart_tics = true;
749 heart_picture_index = ECG_HEART_BAD;
750 } else if (heart_sign_tics >= heart_sign_next_tics) {
751 reset_heart_tics = true;
752
753 if (heart_picture_index == ECG_GRID_PIECE) {
754 heart_picture_index = ECG_HEART_GOOD;
755
756 if (::g_heart_beat_sound) {
757 ::sd_play_player_sound(H_BEATSND, bstone::AC_ITEM);
758 }
759 } else {
760 heart_picture_index = ECG_GRID_PIECE;
761 }
762 }
763
764 if (reset_heart_tics) {
765 heart_sign_tics = 0;
766 heart_sign_next_tics = HEALTH_PULSE / 2;
767 } else {
768 heart_sign_tics += 1;
769 }
770
771 ::CA_CacheGrChunk(
772 static_cast<int16_t>(heart_picture_index));
773
774 ::VWB_DrawPic(
775 120,
776 200 - STATUSLINES + 32,
777 heart_picture_index);
778 }
779
780 // --------------------------------------------------------------------------
781 // DrawHealth()
782 //
783 // PURPOSE : Marks the Health_NUM to be refreshed durring the next
784 // StatusBarRefresh.
785 // --------------------------------------------------------------------------
DrawHealth()786 void DrawHealth()
787 {
788 if (::is_ps()) {
789 auto health_string = std::to_string(gamestate.health);
790
791 std::uninitialized_fill_n(
792 gamestate.health_str,
793 4,
794 '\0');
795
796 auto index = 0;
797
798 for (auto ch : health_string) {
799 gamestate.health_str[index] = ch - '0';
800 index += 1;
801 }
802 }
803
804 DrawHealthNum_COUNT = 3;
805 }
806
DrawHealthNum()807 void DrawHealthNum()
808 {
809 if (!::is_ps()) {
810 ::CA_CacheGrChunk(ECG_GRID_PIECE);
811
812 for (int i = 0; i < 3; ++i) {
813 ::VWB_DrawPic(
814 144 + (i * 8),
815 200 - STATUSLINES + 32,
816 ECG_GRID_PIECE);
817 }
818
819 std::string health_string(4, ' ');
820
821 bstone::StringHelper::lexical_cast(
822 gamestate.health,
823 health_string);
824
825 if (gamestate.health < 100) {
826 health_string.insert(0, 1, ' ');
827
828 if (gamestate.health < 10) {
829 health_string.insert(0, 1, ' ');
830 }
831 }
832
833 health_string += '%';
834
835 fontnumber = 2;
836 fontcolor = 0x9D;
837
838 PrintX = 149;
839 PrintY = 200 - STATUSLINES + 34;
840
841 px = PrintX;
842 py = PrintY;
843
844 VW_DrawPropString(health_string.c_str());
845
846 DrawHealthNum_COUNT -= 1;
847 } else {
848 int8_t loop, num;
849 int16_t check = 100;
850
851 DrawHealthNum_COUNT--;
852
853 for (loop = num = 0; loop < 3; loop++, check /= 10) {
854 if (gamestate.health < check) {
855 LatchDrawPic(16 + loop, 162, NG_BLANKPIC);
856 } else {
857 LatchDrawPic(16 + loop, 162, gamestate.health_str[static_cast<int>(num++)] + NG_0PIC);
858 }
859 }
860 }
861 }
862
TakeDamage(int16_t points,objtype * attacker)863 void TakeDamage(
864 int16_t points,
865 objtype* attacker)
866 {
867 LastAttacker = attacker;
868
869 if (gamestate.flags & GS_ATTACK_INFOAREA) {
870 if (attacker) {
871 if ((LastMsgType == MT_ATTACK) && (LastInfoAttacker == attacker->obclass)) {
872 MsgTicsRemain = DISPLAY_MSG_STD_TIME;
873 } else {
874 if (DISPLAY_TIMED_MSG(ActorInfoMsg[attacker->obclass - rentacopobj], MP_TAKE_DAMAGE, MT_ATTACK)) {
875 LastInfoAttacker = attacker->obclass;
876
877 if (::is_ps()) {
878 LastInfoAttacker_Cloaked = attacker->flags2 & FL2_CLOAKED;
879 }
880 }
881 }
882 }
883 }
884
885 if (godmode) {
886 return;
887 }
888
889 if (gamestate.difficulty == gd_baby) {
890 points >>= 2;
891 }
892
893 gamestate.health -= points;
894
895 if (gamestate.health <= 0) {
896 gamestate.health = 0;
897 playstate = ex_died;
898 killerobj = attacker;
899 if (killerobj) {
900 killerobj->flags |= FL_FREEZE;
901 }
902 }
903
904 StartDamageFlash(points);
905 DrawHealth();
906 }
907
HealSelf(int16_t points)908 void HealSelf(
909 int16_t points)
910 {
911 gamestate.health += points;
912 if (gamestate.health > 100) {
913 gamestate.health = 100;
914 }
915
916 DrawHealth();
917 }
918
919
920 // ===========================================================================
921 //
922 //
923 // SCORE DISPLAY ROUTINES
924 //
925 //
926 // ===========================================================================
927
928 // --------------------------------------------------------------------------
929 // DrawScore()
930 //
931 // PURPOSE : Marks the Score to be refreshed durring the next
932 // StatusBarRefresh.
933 // --------------------------------------------------------------------------
DrawScore()934 void DrawScore()
935 {
936 DrawScoreNum_COUNT = 3;
937 }
938
939 extern uint8_t music_num;
940
941
942 // --------------------------------------------------------------------------
943 // DrawScoreNum()
944 //
945 // NOTE : Could do some sort of "scrolling" animation on LED screen with
946 // chars and a simple table.....
947 // --------------------------------------------------------------------------
DrawScoreNum()948 void DrawScoreNum()
949 {
950 const int Y = 3;
951 const int X = 32;
952
953 if (gamestate.tic_score > MAX_DISPLAY_SCORE) {
954 if (gamestate.score_roll_wait) {
955 LatchDrawPic(X + 0, (200 - STATUSLINES) + Y, N_BLANKPIC);
956 LatchDrawPic(X + 1, (200 - STATUSLINES) + Y, N_DASHPIC);
957 LatchDrawPic(X + 2, (200 - STATUSLINES) + Y, N_RPIC);
958 LatchDrawPic(X + 3, (200 - STATUSLINES) + Y, N_OPIC);
959 LatchDrawPic(X + 4, (200 - STATUSLINES) + Y, N_LPIC);
960 LatchDrawPic(X + 5, (200 - STATUSLINES) + Y, N_LPIC);
961 LatchDrawPic(X + 6, (200 - STATUSLINES) + Y, N_DASHPIC);
962 } else {
963 LatchNumber(X, Y, 7, gamestate.tic_score % (MAX_DISPLAY_SCORE + 1));
964 }
965 } else {
966 if (gamestate.flags & GS_TICS_FOR_SCORE) {
967 LatchNumber(X, Y, 7, realtics);
968 } else if (gamestate.flags & GS_MUSIC_TEST) {
969 LatchNumber(X, Y, 7, music_num);
970 } else {
971 LatchNumber(X, Y, 7, gamestate.tic_score);
972 }
973 }
974 }
975
UpdateScore()976 void UpdateScore()
977 {
978 int32_t score_diff, temp_tics;
979
980 score_diff = gamestate.score - gamestate.tic_score;
981
982 if (score_diff) {
983 if (score_diff > 1500) {
984 temp_tics = score_diff >> 2;
985 } else {
986 temp_tics = tics << 3;
987 }
988
989 if (score_diff > temp_tics) {
990 gamestate.tic_score += temp_tics;
991 } else {
992 gamestate.tic_score = gamestate.score;
993 }
994
995 DrawScore();
996 }
997
998
999 if (gamestate.score_roll_wait) {
1000 if ((gamestate.score_roll_wait -= tics) <= 0) {
1001 gamestate.score_roll_wait = 0;
1002 }
1003 DrawScore();
1004 }
1005 }
1006
1007 // --------------------------------------------------------------------------
1008 // GivePoints()
1009 //
1010 // .score = Holds real score
1011 // .tic_score = Holds displayed score (tic'ing toward .score)
1012 //
1013 // --------------------------------------------------------------------------
GivePoints(int32_t points,bool add_to_stats)1014 void GivePoints(
1015 int32_t points,
1016 bool add_to_stats)
1017 {
1018 // Add score to statistics.
1019 //
1020 if (add_to_stats) {
1021 gamestuff.level[gamestate.mapon].stats.accum_points += points;
1022 }
1023
1024 // Check for bonuses!
1025 //
1026 CheckPinballBonus(points);
1027
1028 // Add points to score
1029 //
1030 gamestate.score += points;
1031 }
1032
1033
1034 // ===========================================================================
1035 //
1036 //
1037 // SECURITY KEY DISPLAY ROUTINES
1038 //
1039 //
1040 // ===========================================================================
1041
1042 // ---------------------------------------------------------------------------
1043 // DrawKeys()
1044 //
1045 // PURPOSE : Marks the security key pics to be refreshed during the next
1046 // StatusBarRefresh.
1047 // ---------------------------------------------------------------------------
DrawKeys()1048 void DrawKeys()
1049 {
1050 DrawKeyPics_COUNT = 3;
1051 }
1052
DrawKeyPics()1053 void DrawKeyPics()
1054 {
1055 ::DrawKeyPics_COUNT -= 1;
1056
1057 if (::is_aog()) {
1058 static const int indices[NUMKEYS] = {
1059 0, 1, 3, 2, 4,
1060 }; // indices
1061
1062 static const uint8_t off_colors[NUMKEYS] = {
1063 0x11, 0x31, 0x91, 0x51, 0x21,
1064 }; // off_colors
1065
1066 static const uint8_t on_colors[NUMKEYS] = {
1067 0xC9, 0xB9, 0x9C, 0x5B, 0x2B,
1068 }; // on_colors
1069
1070 for (auto i = 0; i < NUMKEYS; ++i) {
1071 int index = indices[i];
1072 uint8_t color = 0;
1073
1074 if (gamestate.numkeys[index] > 0) {
1075 color = on_colors[index];
1076 } else {
1077 color = off_colors[index];
1078 }
1079
1080 ::VWB_Bar(
1081 257 + (i * 8),
1082 200 - STATUSLINES + 25,
1083 7,
1084 7,
1085 color);
1086 }
1087 } else {
1088 for (auto loop = 0; loop < 3; loop++) {
1089 if (::gamestate.numkeys[loop]) {
1090 ::LatchDrawPic(15 + 2 * loop, 179, ::RED_KEYPIC + loop);
1091 } else {
1092 ::LatchDrawPic(15 + 2 * loop, 179, ::NO_KEYPIC);
1093 }
1094 }
1095 }
1096 }
1097
GiveKey(int16_t key)1098 void GiveKey(
1099 int16_t key)
1100 {
1101 gamestate.numkeys[key]++;
1102 DrawKeys();
1103 }
1104
TakeKey(int16_t key)1105 void TakeKey(
1106 int16_t key)
1107 {
1108 gamestate.numkeys[key]--;
1109 DrawKeys();
1110 }
1111
1112
1113 // ===========================================================================
1114 //
1115 //
1116 // WEAPON DISPLAY ROUTINES
1117 //
1118 //
1119 // ===========================================================================
1120
1121 // ---------------------------------------------------------------------------
1122 // DrawWeapon()
1123 //
1124 // PURPOSE : Marks the Weapon pics to be refreshed durring the next
1125 // StatusBarRefresh.
1126 // ---------------------------------------------------------------------------
DrawWeapon()1127 void DrawWeapon()
1128 {
1129 DrawWeaponPic_COUNT = 3;
1130 DrawAmmo(true);
1131 }
1132
DrawWeaponPic()1133 void DrawWeaponPic()
1134 {
1135 if (gamestate.weapon == -1) {
1136 return;
1137 }
1138
1139 ::LatchDrawPic(
1140 ::is_ps() ? 31 : 22,
1141 ::is_ps() ? 176 : 152,
1142 WEAPON1PIC + gamestate.weapon);
1143
1144 DrawWeaponPic_COUNT--;
1145 }
1146
GiveWeapon(int weapon)1147 void GiveWeapon(
1148 int weapon)
1149 {
1150 ::GiveAmmo(6);
1151
1152 if ((::gamestate.weapons & (1 << weapon)) == 0)
1153 {
1154 ::gamestate.weapons |= 1 << weapon;
1155
1156 if (::gamestate.weapon < weapon)
1157 {
1158 ::gamestate.weapon = static_cast<int8_t>(weapon);
1159 ::gamestate.chosenweapon = static_cast<int8_t>(weapon);
1160 ::DrawWeapon();
1161 }
1162
1163 ::ComputeAvailWeapons();
1164 }
1165 }
1166
1167
1168 // ===========================================================================
1169 //
1170 //
1171 // AMMO DISPLAY ROUTINES
1172 //
1173 //
1174 // ===========================================================================
1175
1176 // ---------------------------------------------------------------------------
1177 // DrawAmmo()
1178 //
1179 // PURPOSE : Marks the AMMO NUM & AMMO PIC (if necessary) to be refreshed
1180 // durring the next StatusBarRefresh.
1181 //
1182 // NOTE : This re-computes the number of LEDs to be lit.
1183 // ---------------------------------------------------------------------------
DrawAmmo(bool ForceRefresh)1184 void DrawAmmo(
1185 bool ForceRefresh)
1186 {
1187 int16_t temp;
1188 uint16_t ammo, max_ammo;
1189
1190 ComputeAvailWeapons();
1191
1192 //
1193 // Which weapon are we needing a refresh for?
1194 //
1195
1196 switch (gamestate.weapon) {
1197 case wp_autocharge:
1198 DrawAmmoPic_COUNT = 3;
1199 DrawAmmoNum_COUNT = 0;
1200 return;
1201
1202 default:
1203 ammo = gamestate.ammo;
1204 max_ammo = MAX_AMMO;
1205 break;
1206 }
1207
1208 if (ammo) {
1209 temp = (ammo * NUM_AMMO_SEGS) / max_ammo;
1210 if (!temp) {
1211 temp = 1;
1212 }
1213 } else {
1214 temp = 0;
1215 }
1216
1217 gamestate.ammo_leds = static_cast<int8_t>(temp);
1218
1219 if ((temp != gamestate.lastammo_leds) || ForceRefresh) {
1220 gamestate.lastammo_leds = static_cast<int8_t>(temp);
1221 DrawAmmoPic_COUNT = 3;
1222 }
1223
1224 DrawAmmoNum_COUNT = 3;
1225 }
1226
DrawAmmoNum()1227 void DrawAmmoNum()
1228 {
1229 if (gamestate.weapon == -1) {
1230 return;
1231 }
1232
1233 fontnumber = 2;
1234 fontcolor = 0x9D;
1235
1236 PrintX = (::is_ps() ? 252 : 211);
1237 PrintY = 200 - STATUSLINES + 38;
1238
1239 if (::is_ps() || (!::is_ps() && gamestate.weapon != wp_autocharge)) {
1240 ::DrawGAmmoNum();
1241 }
1242
1243 DrawAmmoNum_COUNT--;
1244 }
1245
DrawGAmmoNum()1246 void DrawGAmmoNum()
1247 {
1248 if (gamestate.ammo < 100) {
1249 PrintX += AMMO_SMALL_FONT_NUM_WIDTH;
1250 if (gamestate.ammo < 10) {
1251 PrintX += AMMO_SMALL_FONT_NUM_WIDTH;
1252 }
1253 }
1254
1255 if (::is_ps()) {
1256 ::LatchDrawPic(31, 184, W1_CORNERPIC + gamestate.weapon);
1257 }
1258
1259 px = PrintX;
1260 py = PrintY;
1261
1262 auto ammo_string = std::to_string(gamestate.ammo);
1263 VW_DrawPropString(ammo_string.c_str());
1264 VW_DrawPropString("%");
1265 }
1266
DrawAmmoPic()1267 void DrawAmmoPic()
1268 {
1269 switch (gamestate.weapon) {
1270 case wp_autocharge:
1271 DrawAmmoMsg();
1272 break;
1273
1274 default:
1275 DrawAmmoGuage();
1276 break;
1277 }
1278
1279 DrawAmmoPic_COUNT--;
1280 }
1281
DrawAmmoMsg()1282 void DrawAmmoMsg()
1283 {
1284 int x = (::is_ps() ? 30 : 29);
1285
1286 if (gamestate.weapon_wait) {
1287 LatchDrawPic(x, (200 - STATUSLINES), WAITPIC);
1288 } else {
1289 LatchDrawPic(x, (200 - STATUSLINES), READYPIC);
1290 }
1291 }
1292
DrawPDAmmoMsg()1293 void DrawPDAmmoMsg()
1294 {
1295 if (gamestate.plasma_detonators) {
1296 LatchDrawPic(30, (200 - STATUSLINES), READYPIC);
1297 } else {
1298 LatchDrawPic(30, (200 - STATUSLINES), WAITPIC);
1299 }
1300 }
1301
UpdateAmmoMsg()1302 void UpdateAmmoMsg()
1303 {
1304 if (gamestate.weapon_wait) {
1305 if ((gamestate.weapon_wait -= static_cast<int8_t>(tics)) <= 0) {
1306 gamestate.weapon_wait = 0;
1307 DrawAmmoPic_COUNT = 3;
1308 }
1309 }
1310 }
1311
DrawAmmoGuage()1312 void DrawAmmoGuage()
1313 {
1314 DrawLedStrip(::is_ps() ? 243 : 234, 155, gamestate.ammo_leds, NUM_AMMO_SEGS);
1315 }
1316
UpdateRadarGuage()1317 void UpdateRadarGuage()
1318 {
1319 int16_t temp;
1320
1321 if (gamestate.rpower) {
1322 temp = ((int32_t)gamestate.rpower * NUM_AMMO_SEGS) / MAX_RADAR_ENERGY;
1323
1324 if (temp > NUM_AMMO_SEGS) {
1325 temp = NUM_AMMO_SEGS;
1326 }
1327
1328 if (!temp) {
1329 temp = 1;
1330 }
1331 } else {
1332 temp = 0;
1333 }
1334
1335 gamestate.radar_leds = static_cast<int8_t>(temp);
1336
1337 if (temp != gamestate.lastradar_leds) {
1338 gamestate.lastradar_leds = static_cast<int8_t>(temp);
1339 }
1340
1341 DrawRadarGuage_COUNT = 3;
1342 }
1343
DrawRadarGuage()1344 void DrawRadarGuage()
1345 {
1346 if (!::is_ps()) {
1347 return;
1348 }
1349
1350 int8_t zoom;
1351
1352 DrawLedStrip(235, 155, gamestate.radar_leds, NUM_AMMO_SEGS);
1353
1354 if (gamestate.rpower) {
1355 zoom = gamestate.rzoom;
1356 } else {
1357 zoom = 0;
1358 }
1359
1360 LatchDrawPic(22, 152, ONEXZOOMPIC + zoom);
1361 }
1362
DrawLedStrip(int16_t x,int16_t y,int16_t frac,int16_t max)1363 void DrawLedStrip(
1364 int16_t x,
1365 int16_t y,
1366 int16_t frac,
1367 int16_t max)
1368 {
1369 int16_t ypos;
1370 uint16_t amount;
1371 int8_t leds;
1372
1373 leds = static_cast<int8_t>(frac);
1374
1375 if (leds) {
1376 amount = max - leds;
1377 } else {
1378 amount = max;
1379 }
1380
1381 int width = (::is_ps() ? 5 : 11);
1382
1383 // Draw dim LEDs.
1384 //
1385 for (ypos = 0; ypos < amount; ypos++) {
1386 VW_Hlin(x, x + (width - 1), y++, DimAmmo[0][amount]);
1387 VW_Hlin(x, x + (width - 1), y++, DimAmmo[1][amount]);
1388 }
1389
1390 // Draw lit LEDs.
1391 //
1392 for (; ypos < NUM_AMMO_SEGS; ypos++) {
1393 VW_Hlin(x, x + (width - 1), y++, LitAmmo[0][amount]);
1394 VW_Hlin(x, x + (width - 1), y++, LitAmmo[1][amount]);
1395 }
1396 }
1397
GiveAmmo(int16_t ammo)1398 void GiveAmmo(
1399 int16_t ammo)
1400 {
1401
1402 #if MP_NO_MORE_AMMO > MP_BONUS
1403 if (LastMsgType == MT_OUT_OF_AMMO) {
1404 MsgTicsRemain = 1;
1405 LastMsgType = MT_CLEAR;
1406 }
1407 #endif
1408
1409 gamestate.ammo += ammo;
1410 if (gamestate.ammo > MAX_AMMO) {
1411 gamestate.ammo = MAX_AMMO;
1412 }
1413
1414 DrawAmmo(false);
1415
1416 if (::is_ps()) {
1417 if (gamestate.weapon != gamestate.chosenweapon) {
1418 if (gamestate.useable_weapons & (1 << gamestate.chosenweapon)) {
1419 gamestate.weapon = gamestate.chosenweapon;
1420 DrawWeapon();
1421 }
1422 }
1423 } else {
1424 DrawWeapon();
1425 }
1426
1427 ::sd_play_player_sound(GETAMMOSND, bstone::AC_ITEM);
1428 }
1429
1430
1431 // ---------------------------------------------------------------------------
1432 // ComputeAvailWeapons()
1433 //
1434 // This function creates a Bit MASK for gamestate.weapons according to what
1435 // weapon is available for useage due to ammo avail.
1436 //
1437 // ---------------------------------------------------------------------------
ComputeAvailWeapons()1438 void ComputeAvailWeapons()
1439 {
1440 //
1441 // Determine what ammo ammounts we have avail
1442 //
1443
1444 if (::gamestate.ammo > 0)
1445 {
1446 if (::is_ps() && gamestate.ammo >= BFG_ENERGY_USE)
1447 {
1448 ::gamestate.useable_weapons =
1449 (1 << wp_bfg_cannon) |
1450 (1 << wp_grenade) |
1451 (1 << wp_ion_cannon) |
1452 (1 << wp_burst_rifle) |
1453 (1 << wp_pistol) |
1454 (1 << wp_autocharge);
1455 }
1456 else if (gamestate.ammo >= GRENADE_ENERGY_USE)
1457 {
1458 ::gamestate.useable_weapons =
1459 (1 << wp_grenade) |
1460 (1 << wp_ion_cannon) |
1461 (1 << wp_burst_rifle) |
1462 (1 << wp_pistol) |
1463 (1 << wp_autocharge);
1464 }
1465 else
1466 {
1467 ::gamestate.useable_weapons =
1468 (1 << wp_ion_cannon) |
1469 (1 << wp_burst_rifle) |
1470 (1 << wp_pistol) |
1471 (1 << wp_autocharge);
1472 }
1473 }
1474 else
1475 {
1476 ::gamestate.useable_weapons = 1 << wp_autocharge;
1477 }
1478
1479 //
1480 // mask off with the weapons being carried.
1481 //
1482
1483 gamestate.useable_weapons &= gamestate.weapons;
1484 }
1485
TakePlasmaDetonator(int16_t count)1486 void TakePlasmaDetonator(
1487 int16_t count)
1488 {
1489 if (gamestate.plasma_detonators < count) {
1490 gamestate.plasma_detonators = 0;
1491 } else {
1492 gamestate.plasma_detonators -= count;
1493 }
1494 }
1495
GivePlasmaDetonator(int16_t count)1496 void GivePlasmaDetonator(
1497 int16_t count)
1498 {
1499 gamestate.plasma_detonators += count;
1500
1501 if (gamestate.plasma_detonators > MAX_PLASMA_DETONATORS) {
1502 gamestate.plasma_detonators = MAX_PLASMA_DETONATORS;
1503 }
1504
1505 ComputeAvailWeapons();
1506 }
1507
GiveToken(int16_t tokens)1508 void GiveToken(
1509 int16_t tokens)
1510 {
1511 #if MP_NO_MORE_TOKENS > MP_BONUS
1512 if (LastMsgType == MT_NO_MO_FOOD_TOKENS) {
1513 MsgTicsRemain = 1;
1514 LastMsgType = MT_CLEAR;
1515 }
1516 #endif
1517
1518 gamestate.tokens += tokens;
1519 if (gamestate.tokens > MAX_TOKENS) {
1520 gamestate.tokens = MAX_TOKENS;
1521 }
1522
1523 ::sd_play_player_sound(GOTTOKENSND, bstone::AC_ITEM);
1524 }
1525
1526
1527 // ===========================================================================
1528 //
1529 //
1530 // INFO AREA ROUTINES
1531 //
1532 //
1533 // ===========================================================================
1534
1535 // --------------------------------------------------------------------------
1536 // DisplayInfoMsg() - Returns if Higher Pri message is holding.
1537 //
1538 // SEE MACROS: DISPLAY_TIMED_MSG() & DISPLAY_MSG() -- Def.h
1539 //
1540 // DISPLAY_TIMED_MSG(msg,pri,type) - For E-Z Timed Msgs (std. display time)
1541 // DISPLAY_MSG(msg,pri,type) - For E-Z NON-Timed Msgs.
1542 // --------------------------------------------------------------------------
DisplayInfoMsg(const std::string & Msg,msg_priorities Priority,int16_t DisplayTime,int16_t MsgType)1543 bool DisplayInfoMsg(
1544 const std::string& Msg,
1545 msg_priorities Priority,
1546 int16_t DisplayTime,
1547 int16_t MsgType)
1548 {
1549 if (Msg.empty()) {
1550 return false;
1551 }
1552
1553 return ::DisplayInfoMsg(
1554 Msg.c_str(),
1555 Priority,
1556 DisplayTime,
1557 MsgType);
1558 }
1559
DisplayInfoMsg(const char * Msg,msg_priorities Priority,int16_t DisplayTime,int16_t MsgType)1560 bool DisplayInfoMsg(
1561 const char* Msg,
1562 msg_priorities Priority,
1563 int16_t DisplayTime,
1564 int16_t MsgType)
1565 {
1566 if (Priority >= LastMsgPri) {
1567 if (Priority == MP_max_val) { // "System" msgs
1568 LastMsgPri = MP_min_val;
1569 } else {
1570 LastMsgPri = static_cast<uint16_t>(Priority);
1571 }
1572
1573 if ((MsgTicsRemain = DisplayTime) != 0) {
1574 StatusAllDrawPic(0, 40, BRI_LIGHTPIC);
1575 }
1576
1577 gamestate.msg = Msg;
1578
1579 DrawInfoArea_COUNT = InitInfoArea_COUNT = 3;
1580
1581 LastMsgType = static_cast<infomsg_type>(MsgType);
1582
1583 if (::is_ps() && LastMsgType != MT_ATTACK) {
1584 LastInfoAttacker_Cloaked = 0;
1585 }
1586
1587 return true;
1588 } else {
1589 return false;
1590 }
1591 }
1592
ClearInfoArea()1593 void ClearInfoArea()
1594 {
1595 if (ClearInfoArea_COUNT) {
1596 ClearInfoArea_COUNT--;
1597 }
1598
1599 InfoAreaSetup.x = InfoAreaSetup.left_margin;
1600 InfoAreaSetup.y = INFOAREA_Y;
1601 InfoAreaSetup.framecount = InfoAreaSetup.numanims = 0;
1602
1603 LatchDrawPic(0, 200 - STATUSLINES, INFOAREAPIC);
1604 }
1605
InitInfoArea()1606 void InitInfoArea()
1607 {
1608 InfoAreaSetup.left_margin = INFOAREA_X;
1609 InfoAreaSetup.text_color = INFOAREA_TCOLOR;
1610 InfoAreaSetup.backgr_color = INFOAREA_BCOLOR;
1611 InitInfoArea_COUNT--;
1612
1613 ClearInfoArea();
1614 }
1615
UpdateInfoArea()1616 void UpdateInfoArea()
1617 {
1618 if (InfoAreaSetup.numanims) {
1619 AnimatePage();
1620 }
1621
1622 if (InitInfoArea_COUNT) {
1623 InitInfoArea();
1624 } else if (ClearInfoArea_COUNT) {
1625 ClearInfoArea();
1626 }
1627
1628 if (DrawInfoArea_COUNT) {
1629 DrawInfoArea();
1630 }
1631 }
1632
1633 // ---------------------------------------------------------------------------
1634 // UpdateInfoAreaClock() - This routine is called ONLY ONCE per refresh
1635 // to update the InfoArea Clock and to release
1636 // any messages that have expired.
1637 // ---------------------------------------------------------------------------
UpdateInfoAreaClock()1638 void UpdateInfoAreaClock()
1639 {
1640
1641 if (playstate == ex_title || playstate == ex_victorious) {
1642 return;
1643 }
1644
1645 //
1646 // Check for existing timed messages
1647 //
1648
1649 if (LastMsgPri && MsgTicsRemain) {
1650 //
1651 // Tic' that 'Puppy' down - Yea!
1652 //
1653
1654 if ((MsgTicsRemain -= tics) <= 0) {
1655 // Message has expired.
1656 DisplayNoMoMsgs();
1657 }
1658 }
1659
1660 }
1661
1662 char default_msg[] = { "\r NO MESSAGES."
1663 "^FCA8\r FOOD TOKENS: "
1664 " " };
1665
1666 char needDetonator_msg[] = "\r\r^FC39 FIND THE DETONATOR!";
1667
1668 char haveDetonator_msg[] = "\r\r^FC39DESTROY SECURITY CUBE!";
1669
1670 char destroyGoldfire_msg[] = "\r\r^FC39 DESTROY GOLDFIRE!";
1671
DisplayNoMoMsgs()1672 void DisplayNoMoMsgs()
1673 {
1674 LastMsgPri = MP_min_val;
1675
1676 if (BONUS_QUEUE) {
1677 DisplayPinballBonus();
1678 return;
1679 }
1680
1681 MsgTicsRemain = 0;
1682 StatusAllDrawPic(0, 40, DIM_LIGHTPIC);
1683 sprintf((char*)&default_msg[40], "%-d", gamestate.tokens);
1684 if (gamestuff.level[gamestate.mapon + 1].locked) {
1685 switch (gamestate.mapon) {
1686 case 19:
1687 if (::is_ps()) {
1688 strcat(default_msg, destroyGoldfire_msg);
1689 }
1690 break;
1691
1692 case 20:
1693 case 21:
1694 case 22:
1695 case 23:
1696 break;
1697
1698 default:
1699 if (::is_ps()) {
1700 if (gamestate.plasma_detonators) {
1701 strcat(default_msg, haveDetonator_msg);
1702 } else {
1703 strcat(default_msg, needDetonator_msg);
1704 }
1705 }
1706 break;
1707 }
1708 }
1709
1710 DisplayInfoMsg(default_msg, MP_max_val, 0, MT_NOTHING);
1711 }
1712
1713 // --------------------------------------------------------------------------
1714 // DrawInfoArea()
1715 //
1716 //
1717 // Active control codes:
1718 //
1719 // ^ANnn - define animation
1720 // ^FCnn - set font color
1721 // ^LMnnn - set left margin (if 'nnn' == "fff" uses current x)
1722 // ^EP - end of page (waits for 'M' to read MORE)
1723 // ^PXnnn - move x to coordinate 'n'
1724 // ^PYnnn - move y to coordinate 'n'
1725 // ^SHnnn - display shape 'n' at current x,y
1726 // ^BGn - set background color
1727 // ^DM - Default Margins
1728 //
1729 // Other info:
1730 //
1731 // All 'n' values are hex numbers (0 - f), case insensitive.
1732 // The number of N's listed is the number of digits REQUIRED by that control
1733 // code. (IE: ^LMnnn MUST have 3 values! --> 003, 1a2, 01f, etc...)
1734 //
1735 // If a line consists only of control codes, the cursor is NOT advanced
1736 // to the next line (the ending <CR><LF> is skipped). If just ONE non-control
1737 // code is added, the number "8" for example, then the "8" is displayed
1738 // and the cursor is advanced to the next line.
1739 //
1740 // The text presenter now handles sprites, but they are NOT masked! Also,
1741 // sprite animations will be difficult to implement unless all frames are
1742 // of the same dimensions.
1743 //
1744 // --------------------------------------------------------------------------
1745
1746 char* HandleControlCodes(
1747 char* first_ch);
1748
1749
DrawInfoArea()1750 void DrawInfoArea()
1751 {
1752 const int16_t IA_FONT_HEIGHT = 6;
1753
1754 char* first_ch;
1755 char* scan_ch, temp;
1756
1757 DrawInfoArea_COUNT--;
1758
1759 if (!gamestate.msg) {
1760 return;
1761 }
1762
1763 if (!*gamestate.msg) {
1764 return;
1765 }
1766
1767 std::vector<char> buffer(
1768 gamestate.msg,
1769 gamestate.msg + std::string::traits_type::length(gamestate.msg) + 1);
1770
1771 first_ch = &buffer[0];
1772
1773 fontnumber = 2;
1774 fontcolor = static_cast<uint8_t>(InfoAreaSetup.text_color);
1775
1776 while (first_ch && *first_ch) {
1777
1778 if (*first_ch != TP_CONTROL_CHAR) {
1779 scan_ch = first_ch;
1780
1781 while ((*scan_ch) && (*scan_ch != '\n') && (*scan_ch != TP_RETURN_CHAR) && (*scan_ch != TP_CONTROL_CHAR)) {
1782 scan_ch++;
1783 }
1784
1785 // print current line
1786 //
1787
1788 temp = *scan_ch;
1789 *scan_ch = 0;
1790
1791 if (*first_ch != TP_RETURN_CHAR) {
1792 int8_t temp_color;
1793
1794 temp_color = fontcolor;
1795 fontcolor = INFOAREA_TSHAD_COLOR;
1796
1797 px = InfoAreaSetup.x + 1;
1798 py = InfoAreaSetup.y + 1;
1799 VW_DrawPropString(first_ch);
1800 fontcolor = temp_color;
1801
1802 px = InfoAreaSetup.x;
1803 py = InfoAreaSetup.y;
1804 VW_DrawPropString(first_ch);
1805 }
1806
1807 *scan_ch = temp;
1808 first_ch = scan_ch;
1809
1810 // skip SPACES / RETURNS at end of line
1811 //
1812
1813 if ((*first_ch == ' ') || (*first_ch == TP_RETURN_CHAR)) {
1814 first_ch++;
1815 }
1816
1817 // TP_CONTROL_CHARs don't advance to next character line
1818 //
1819
1820 if (*scan_ch != TP_CONTROL_CHAR) {
1821 InfoAreaSetup.x = InfoAreaSetup.left_margin;
1822 InfoAreaSetup.y += IA_FONT_HEIGHT;
1823 } else {
1824 InfoAreaSetup.x = px;
1825 }
1826 } else {
1827 first_ch = HandleControlCodes(first_ch);
1828 }
1829 }
1830 }
1831
HandleControlCodes(char * first_ch)1832 char* HandleControlCodes(
1833 char* first_ch)
1834 {
1835 piShapeInfo* shape;
1836 piAnimInfo* anim;
1837 uint16_t shapenum;
1838
1839 first_ch++;
1840
1841 #ifndef TP_CASE_SENSITIVE
1842 *first_ch = toupper(*first_ch);
1843 *(first_ch + 1) = toupper(*(first_ch + 1));
1844 #endif
1845
1846 uint16_t code = *reinterpret_cast<const uint16_t*>(first_ch);
1847 first_ch += 2;
1848
1849 switch (code) {
1850
1851 // INIT ANIMATION ---------------------------------------------------
1852 //
1853 case TP_CNVT_CODE('A', 'N'):
1854 shapenum = TP_VALUE(first_ch, 2);
1855 first_ch += 2;
1856 memcpy(&piAnimList[static_cast<int>(InfoAreaSetup.numanims)], &piAnimTable[shapenum], sizeof(piAnimInfo));
1857 anim = &piAnimList[static_cast<int>(InfoAreaSetup.numanims++)];
1858 shape = &piShapeTable[anim->baseshape + anim->frame]; // BUG!! (assumes "pia_shapetable")
1859
1860 anim->y = InfoAreaSetup.y;
1861 anim->x = DrawShape(InfoAreaSetup.x, InfoAreaSetup.y, shape->shapenum, shape->shapetype);
1862 InfoAreaSetup.framecount = 3;
1863 InfoAreaSetup.left_margin = InfoAreaSetup.x;
1864 break;
1865
1866 // DRAW SHAPE -------------------------------------------------------
1867 //
1868 case TP_CNVT_CODE('S', 'H'):
1869
1870 // NOTE : This needs to handle the left margin....
1871
1872 shapenum = TP_VALUE(first_ch, 3);
1873 first_ch += 3;
1874 shape = &piShapeTable[shapenum];
1875
1876 DrawShape(InfoAreaSetup.x, InfoAreaSetup.y, shape->shapenum, shape->shapetype);
1877 InfoAreaSetup.left_margin = InfoAreaSetup.x;
1878 break;
1879
1880 // FONT COLOR -------------------------------------------------------
1881 //
1882 case TP_CNVT_CODE('F', 'C'):
1883 InfoAreaSetup.text_color = TP_VALUE(first_ch, 2);
1884 fontcolor = static_cast<uint8_t>(TP_VALUE(first_ch, 2));
1885 first_ch += 2;
1886 break;
1887
1888 // BACKGROUND COLOR -------------------------------------------------
1889 //
1890 case TP_CNVT_CODE('B', 'G'):
1891 InfoAreaSetup.backgr_color = TP_VALUE(first_ch, 2);
1892 first_ch += 2;
1893 break;
1894
1895 // DEFAULT MARGINS -------------------------------------------------
1896 //
1897 case TP_CNVT_CODE('D', 'M'):
1898 InfoAreaSetup.left_margin = INFOAREA_X;
1899 break;
1900
1901 // LEFT MARGIN ------------------------------------------------------
1902 //
1903 case TP_CNVT_CODE('L', 'M'):
1904 shapenum = TP_VALUE(first_ch, 3);
1905 first_ch += 3;
1906 if (shapenum == 0xfff) {
1907 InfoAreaSetup.left_margin = InfoAreaSetup.x;
1908 } else {
1909 InfoAreaSetup.left_margin = shapenum;
1910 }
1911 break;
1912 }
1913
1914 return first_ch;
1915
1916 }
1917
DrawShape(int16_t x,int16_t y,int16_t shapenum,pisType shapetype)1918 int16_t DrawShape(
1919 int16_t x,
1920 int16_t y,
1921 int16_t shapenum,
1922 pisType shapetype)
1923 {
1924 int16_t width = 0;
1925 uint16_t shade;
1926
1927 //
1928 // If Image is Cloaked... Shade the image
1929 //
1930 if (LastInfoAttacker_Cloaked) {
1931 shade = 35; // 63 == BLACK | 0 == NO SHADING
1932 } else
1933 shade = 0;
1934
1935 switch (shapetype) {
1936 case pis_scaled:
1937 VW_Bar(x, y, 37, 37, static_cast<uint8_t>(InfoAreaSetup.backgr_color)); // JTR changed
1938 ::vid_draw_ui_sprite(shapenum, x + 19, y + 20, 37);
1939 width = 37;
1940 break;
1941
1942 case pis_latchpic:
1943 x = (x + 7) & 0xFFF8;
1944 LatchDrawPic(x >> 3, y, shapenum);
1945 break;
1946
1947 case pis_pic:
1948 x = (x + 7) & 0xFFF8;
1949 width = pictable[shapenum - STARTPICS].width;
1950 CA_MarkGrChunk(shapenum);
1951 CA_CacheMarks();
1952 VWB_DrawPic(x, y, shapenum);
1953 UNCACHEGRCHUNK(shapenum);
1954 break;
1955
1956 default:
1957 break;
1958 }
1959
1960 InfoAreaSetup.x += width;
1961 return x;
1962 }
1963
AnimatePage()1964 void AnimatePage()
1965 {
1966 piAnimInfo* anim = piAnimList;
1967 piShapeInfo* shape;
1968
1969 // Dec Timers
1970 //
1971
1972 anim->delay += tics;
1973
1974 if (anim->delay >= anim->maxdelay) {
1975 InfoAreaSetup.framecount = 3;
1976 anim->delay = 0;
1977 }
1978
1979 // Test framecount - Do we need to draw a shape?
1980 //
1981
1982 if (InfoAreaSetup.framecount) {
1983 // Draw shapes
1984
1985 switch (anim->animtype) {
1986 case pia_shapetable:
1987 shape = &piShapeTable[anim->baseshape + anim->frame];
1988 DrawShape(anim->x, anim->y, shape->shapenum, shape->shapetype);
1989 break;
1990
1991 case pia_grabscript:
1992 shape = &piShapeTable[anim->baseshape];
1993 DrawShape(anim->x, anim->y, shape->shapenum + anim->frame, shape->shapetype);
1994 break;
1995 }
1996
1997 // Dec frame count
1998
1999 InfoAreaSetup.framecount--;
2000 if (!InfoAreaSetup.framecount) {
2001 // Have drawn all pages... Inc Frame count
2002
2003 anim->frame++;
2004 if (anim->frame == anim->maxframes) {
2005 anim->frame = 0;
2006 }
2007 }
2008 }
2009 }
2010
2011
2012 // ===========================================================================
2013 //
2014 //
2015 // STATUS BAR REFRESH ROUTINES
2016 //
2017 //
2018 // ===========================================================================
2019
UpdateStatusBar()2020 void UpdateStatusBar()
2021 {
2022 if (playstate == ex_title || playstate == ex_victorious) {
2023 return;
2024 }
2025
2026
2027 //
2028 // Call specific status bar managers
2029 //
2030
2031 UpdateScore();
2032 UpdateInfoArea();
2033
2034 //
2035 // Refresh Status Area
2036 //
2037
2038 if (DrawAmmoPic_COUNT) {
2039 DrawAmmoPic();
2040 }
2041
2042 DrawScoreNum();
2043
2044 if (DrawWeaponPic_COUNT) {
2045 DrawWeaponPic();
2046 }
2047
2048 if (DrawRadarGuage_COUNT) {
2049 DrawRadarGuage();
2050 }
2051
2052 DrawAmmoNum();
2053
2054 if (DrawKeyPics_COUNT) {
2055 DrawKeyPics();
2056 }
2057
2058 if (DrawHealthNum_COUNT) {
2059 DrawHealthNum();
2060 }
2061
2062 if (gamestate.flags & (GS_TICS_FOR_SCORE)) {
2063 DrawScore();
2064 }
2065
2066 if (!::is_ps()) {
2067 ::DrawHealthMonitor();
2068 }
2069 }
2070
2071 // ---------------------------------------------------------------------------
2072 // ForceUpdateStatusBar() - Force Draw status bar onto ALL display pages
2073 // ---------------------------------------------------------------------------
ForceUpdateStatusBar()2074 void ForceUpdateStatusBar()
2075 {
2076 ::DrawScore();
2077 ::DrawWeapon();
2078 ::DrawKeys();
2079 ::DrawHealth();
2080 ::UpdateRadarGuage();
2081 ::UpdateStatusBar();
2082 }
2083
2084
2085 /*
2086 ===================
2087 =
2088 = GetBonus
2089 =
2090 ===================
2091 */
2092
2093 uint16_t static_points[] = { 100, // money bag
2094 500, // loot
2095 250, // gold1
2096 500, // gold2
2097 750, // gold3
2098 1000, // major gold!
2099 5000 // bonus
2100 };
2101
2102 using StaticHealthTable = std::vector<std::array<int16_t, 3>>;
2103
2104 StaticHealthTable static_health;
2105
initialize_static_health_table()2106 void initialize_static_health_table()
2107 {
2108 static_health = {
2109 { 100, HEALTH2SND, -1, }, // Full Heal
2110 { 30, HEALTH1SND, -1, }, // First Aid
2111 { 20, HEALTH1SND, SPR_STAT_45, }, // Steak
2112 { 15, HEALTH1SND, SPR_STAT_43, }, // Chicken Leg
2113 { 10, HEALTH1SND, SPR_SANDWICH_WRAPER, }, // Sandwich
2114 { 8, HEALTH1SND, SPR_CANDY_WRAPER, }, // Candy Bar
2115 { 5, HEALTH1SND, SPR_STAT_41, }, // Water bowl
2116 { 5, HEALTH1SND, -1, }, // Water puddle
2117 };
2118 }
2119
2120 extern std::string bonus_msg24;
2121 extern std::string bonus_msg25;
2122
GetBonus(statobj_t * check)2123 void GetBonus(
2124 statobj_t* check)
2125 {
2126 bool givepoints = false;
2127 int16_t shapenum = -1;
2128
2129 switch (check->itemnumber) {
2130 case bo_red_key:
2131 case bo_yellow_key:
2132 case bo_blue_key:
2133 case bo_green_key:
2134 case bo_gold_key:
2135 {
2136 uint16_t keynum = 0;
2137
2138 if (::is_aog()) {
2139 switch (check->itemnumber) {
2140 case bo_red_key:
2141 keynum = 0;
2142 break;
2143
2144 case bo_yellow_key:
2145 keynum = 1;
2146 break;
2147
2148 case bo_blue_key:
2149 keynum = 2;
2150 break;
2151
2152 case bo_green_key:
2153 keynum = 3;
2154 break;
2155
2156 case bo_gold_key:
2157 keynum = 4;
2158 break;
2159 }
2160 } else {
2161 keynum = check->itemnumber - bo_red_key;
2162 }
2163
2164 if (gamestate.numkeys[keynum] >= MAXKEYS) {
2165 return;
2166 }
2167
2168 GiveKey(keynum);
2169
2170 ::sd_play_player_sound(GETKEYSND, bstone::AC_ITEM);
2171
2172 TravelTable[check->tilex][check->tiley] &= ~TT_KEYS;
2173 break;
2174 }
2175
2176 case bo_money_bag:
2177 ::sd_play_player_sound(BONUS1SND, bstone::AC_ITEM);
2178 givepoints = true;
2179 break;
2180
2181 case bo_loot:
2182 ::sd_play_player_sound(BONUS2SND, bstone::AC_ITEM);
2183
2184 givepoints = true;
2185 break;
2186
2187
2188 case bo_gold1:
2189 case bo_gold2:
2190 case bo_gold3:
2191 case bo_gold:
2192 ::sd_play_player_sound(BONUS3SND, bstone::AC_ITEM);
2193 givepoints = true;
2194 break;
2195
2196
2197 case bo_bonus:
2198 ::sd_play_player_sound(BONUS4SND, bstone::AC_ITEM);
2199 givepoints = true;
2200 break;
2201
2202 case bo_water_puddle:
2203 if (gamestate.health > 15) {
2204 return;
2205 }
2206 case bo_fullheal:
2207 case bo_firstaid:
2208 case bo_ham: // STEAK
2209 case bo_chicken:
2210 case bo_sandwich:
2211 case bo_candybar:
2212 case bo_water:
2213 if (gamestate.health == 100) {
2214 return;
2215 }
2216
2217 ::sd_play_player_sound(static_health[check->itemnumber - bo_fullheal][1],
2218 bstone::AC_ITEM);
2219
2220 HealSelf(static_health[check->itemnumber - bo_fullheal][0]);
2221 check->flags &= ~FL_BONUS;
2222 shapenum = static_health[check->itemnumber - bo_fullheal][2];
2223 break;
2224
2225 case bo_clip:
2226 if (gamestate.ammo == MAX_AMMO) {
2227 return;
2228 }
2229 GiveAmmo(8);
2230 bonus_msg7[45] = '8';
2231 break;
2232
2233 case bo_clip2: {
2234 uint8_t ammo;
2235
2236 if (gamestate.ammo == MAX_AMMO) {
2237 return;
2238 }
2239
2240 ammo = 1 + (US_RndT() & 0x7);
2241 bonus_msg7[45] = '0' + ammo;
2242 GiveAmmo(ammo);
2243 }
2244 break;
2245
2246 case bo_plasma_detonator:
2247 TravelTable[check->tilex][check->tiley] &= ~TT_KEYS;
2248 GivePlasmaDetonator(1);
2249 ::sd_play_player_sound(GETDETONATORSND, bstone::AC_ITEM);
2250 break;
2251
2252 case bo_pistol:
2253 ::sd_play_player_sound(GETPISTOLSND, bstone::AC_ITEM);
2254 GiveWeapon(wp_pistol);
2255 break;
2256
2257 case bo_burst_rifle:
2258 ::sd_play_player_sound(GETBURSTRIFLESND, bstone::AC_ITEM);
2259 GiveWeapon(wp_burst_rifle);
2260 break;
2261
2262 case bo_ion_cannon:
2263 ::sd_play_player_sound(GETIONCANNONSND, bstone::AC_ITEM);
2264 GiveWeapon(wp_ion_cannon);
2265 break;
2266
2267 case bo_grenade:
2268 ::sd_play_player_sound(GETCANNONSND, bstone::AC_ITEM);
2269 GiveWeapon(wp_grenade);
2270 break;
2271
2272 case bo_bfg_cannon:
2273 ::sd_play_player_sound(GETCANNONSND, bstone::AC_ITEM);
2274 GiveWeapon(wp_bfg_cannon);
2275 break;
2276
2277 case bo_coin:
2278 if (gamestate.tokens == MAX_TOKENS) {
2279 return;
2280 }
2281 GiveToken(1);
2282
2283 writeTokenStr(bonus_msg24);
2284 break;
2285
2286 case bo_coin5:
2287 if (gamestate.tokens == MAX_TOKENS) {
2288 return;
2289 }
2290 GiveToken(5);
2291
2292 writeTokenStr(bonus_msg25);
2293 break;
2294
2295 case bo_automapper1:
2296 if (gamestate.rpower > MAX_RADAR_ENERGY - (RADAR_PAK_VALUE / 8)) {
2297 return;
2298 }
2299 gamestate.rpower += RADAR_PAK_VALUE;
2300
2301 ::sd_play_player_sound(RADAR_POWERUPSND, bstone::AC_ITEM);
2302
2303 UpdateRadarGuage();
2304 break;
2305 }
2306
2307 if (givepoints) {
2308 GivePoints(static_points[check->itemnumber - bo_money_bag], true);
2309 }
2310
2311 DISPLAY_TIMED_MSG(BonusMsg[check->itemnumber - 1], MP_BONUS, MT_BONUS);
2312 StartBonusFlash();
2313 check->shapenum = shapenum; // remove from list if shapenum == -1
2314 check->itemnumber = bo_nothing;
2315 }
2316
writeTokenStr(std::string & string)2317 void writeTokenStr(
2318 std::string& string)
2319 {
2320 auto token_string = std::to_string(gamestate.tokens);
2321
2322 if (token_string.length() == 1) {
2323 token_string = '0' + token_string;
2324 }
2325
2326 string.erase(string.length() - 2, 2);
2327 string += token_string;
2328 }
2329
2330
2331 /*
2332 ===================
2333 =
2334 = TryMove
2335 =
2336 = returns true if move ok
2337 = debug: use pointers to optimize
2338 ===================
2339 */
TryMove(objtype * ob)2340 bool TryMove(
2341 objtype* ob)
2342 {
2343 int16_t xl, yl, xh, yh, x, y, xx, yy;
2344 objtype* check;
2345 int32_t deltax, deltay;
2346
2347 if (ob == player) {
2348 xl = (ob->x - PLAYERSIZE) >> TILESHIFT;
2349 yl = (ob->y - PLAYERSIZE) >> TILESHIFT;
2350 xh = (ob->x + PLAYERSIZE) >> TILESHIFT;
2351 yh = (ob->y + PLAYERSIZE) >> TILESHIFT;
2352 } else {
2353 if (ob->obclass == blakeobj) {
2354 xl = (ob->x - (0x1000l)) >> TILESHIFT;
2355 yl = (ob->y - (0x1000l)) >> TILESHIFT;
2356 xh = (ob->x + (0x1000l)) >> TILESHIFT;
2357 yh = (ob->y + (0x1000l)) >> TILESHIFT;
2358 } else {
2359 xl = (ob->x - (0x7FFFl)) >> TILESHIFT;
2360 yl = (ob->y - (0x7FFFl)) >> TILESHIFT;
2361 xh = (ob->x + (0x7FFFl)) >> TILESHIFT;
2362 yh = (ob->y + (0x7FFFl)) >> TILESHIFT;
2363 }
2364 }
2365
2366 //
2367 // check for solid walls
2368 //
2369 for (y = yl; y <= yh; y++) {
2370 for (x = xl; x <= xh; x++) {
2371 check = actorat[x][y];
2372
2373 if (check) {
2374 if ((check < objlist) || (check->flags & FL_FAKE_STATIC)) {
2375 return false;
2376 }
2377 }
2378 }
2379 }
2380
2381 //
2382 // check for actors....
2383 //
2384 yl -= 2;
2385 yh += 2;
2386 xl -= 2;
2387 xh += 2;
2388
2389 // NOTE: xl,yl may go NEGITIVE!
2390 // ---- xh,yh may exceed 63 (MAPWIDTH-1)
2391
2392 for (y = yl; y <= yh; y++) {
2393 for (x = xl; x <= xh; x++) {
2394 xx = x & 0x3f;
2395
2396 yy = y & 0x3f;
2397
2398 check = actorat[xx][yy];
2399
2400 if ((check > objlist) && ((check->flags & (FL_SOLID | FL_FAKE_STATIC)) == FL_SOLID)) {
2401 deltax = ob->x - check->x;
2402 if ((deltax < -MINACTORDIST) || (deltax > MINACTORDIST)) {
2403 continue;
2404 }
2405
2406 deltay = ob->y - check->y;
2407 if ((deltay < -MINACTORDIST) || (deltay > MINACTORDIST)) {
2408 continue;
2409 }
2410
2411 return false;
2412 }
2413 }
2414 }
2415
2416 return true;
2417 }
2418
2419
2420 /*
2421 ===================
2422 =
2423 = ClipMove
2424 =
2425 = returns true if object hit a wall
2426 =
2427 ===================
2428 */
ClipMove(objtype * ob,int32_t xmove,int32_t ymove)2429 bool ClipMove(
2430 objtype* ob,
2431 int32_t xmove,
2432 int32_t ymove)
2433 {
2434 int32_t basex, basey;
2435
2436 basex = ob->x;
2437 basey = ob->y;
2438
2439 ob->x = (basex + xmove);
2440 ob->y = (basey + ymove);
2441
2442 if (TryMove(ob)) {
2443 return false;
2444 }
2445
2446 if (!g_no_wall_hit_sound) {
2447 if (!::sd_is_player_channel_playing(bstone::AC_HIT_WALL)) {
2448 ::sd_play_player_sound(HITWALLSND, bstone::AC_HIT_WALL);
2449 }
2450 }
2451
2452 ob->x = (basex + xmove);
2453 ob->y = basey;
2454
2455 if (TryMove(ob)) {
2456 return true;
2457 }
2458
2459 ob->x = basex;
2460 ob->y = (basey + ymove);
2461
2462
2463 if (TryMove(ob)) {
2464 return true;
2465 }
2466
2467 ob->x = basex;
2468 ob->y = basey;
2469
2470 return true;
2471 }
2472
2473
2474 /*
2475 ===================
2476 =
2477 = Thrust
2478 =
2479 ===================
2480 */
Thrust(int16_t angle,int32_t speed)2481 void Thrust(
2482 int16_t angle,
2483 int32_t speed)
2484 {
2485 extern uint8_t TravelTable[MAPSIZE][MAPSIZE];
2486 objtype dumb;
2487 int32_t xmove, ymove;
2488 uint16_t offset, * map[2];
2489 int16_t dx, dy;
2490 int16_t dangle;
2491 bool ignore_map1;
2492
2493 thrustspeed += speed;
2494 //
2495 // moving bounds speed
2496 //
2497 if (speed >= MINDIST * 2) {
2498 speed = MINDIST * 2 - 1;
2499 }
2500
2501 xmove = FixedByFrac(speed, costable[angle]);
2502 ymove = -FixedByFrac(speed, sintable[angle]);
2503
2504 ClipMove(player, xmove, ymove);
2505
2506 player_oldtilex = player->tilex;
2507 player_oldtiley = player->tiley;
2508 player->tilex = static_cast<uint8_t>(player->x >> TILESHIFT); // scale to tile values
2509 player->tiley = static_cast<uint8_t>(player->y >> TILESHIFT);
2510
2511 player->areanumber = GetAreaNumber(player->tilex, player->tiley);
2512 areabyplayer[player->areanumber] = true;
2513 TravelTable[player->tilex][player->tiley] |= TT_TRAVELED;
2514
2515 offset = farmapylookup[player->tiley] + player->tilex;
2516 map[0] = mapsegs[0] + offset;
2517 map[1] = mapsegs[1] + offset;
2518
2519 // Check for trigger tiles.
2520 //
2521 switch (*map[0]) {
2522 case DOORTRIGGERTILE:
2523 dx = *map[1] >> 8; // x
2524 dy = *map[1] & 255; // y
2525 if (OperateSmartSwitch(dx, dy, ST_TOGGLE, false)) { // Operate & Check for removeal
2526 *map[0] = AREATILE + player->areanumber; // Remove switch
2527 }
2528 ignore_map1 = true;
2529 break;
2530
2531 case SMART_OFF_TRIGGER:
2532 case SMART_ON_TRIGGER:
2533 dx = *map[1] >> 8;
2534 dy = *map[1] & 255;
2535 OperateSmartSwitch(dx, dy, static_cast<int8_t>((*map[0]) - SMART_OFF_TRIGGER), false);
2536 ignore_map1 = true;
2537 break;
2538
2539 case WINTIGGERTILE:
2540 playstate = ex_victorious;
2541 dumb.x = ((int32_t)gamestate.wintilex << TILESHIFT) + TILEGLOBAL / 2;
2542 dumb.y = ((int32_t)gamestate.wintiley << TILESHIFT) + TILEGLOBAL / 2;
2543 dumb.flags = 0;
2544 dangle = CalcAngle(player, &dumb);
2545 RotateView(dangle, 2);
2546 if (!::is_ps()) {
2547 RunBlakeRun();
2548 }
2549 ignore_map1 = true;
2550 break;
2551
2552 default:
2553 ignore_map1 = false;
2554 break;
2555 }
2556
2557 if (!ignore_map1) {
2558 // Change sky and ground color on-the-fly.
2559 //
2560
2561 offset = *(map[1] + 1); // 'offset' used as temp...
2562 switch (*map[1]) {
2563 case 0xFE00:
2564 TopColor = offset & 0xFF00;
2565 TopColor |= TopColor >> 8;
2566 BottomColor = offset & 0xFF;
2567 BottomColor |= BottomColor << 8;
2568 break;
2569 }
2570 }
2571
2572 }
2573
2574 bool GAN_HiddenArea;
2575
GetAreaNumber(int tilex,int tiley)2576 int8_t GetAreaNumber(
2577 int tilex,
2578 int tiley)
2579 {
2580 ::GAN_HiddenArea = false;
2581
2582 // Are we on a wall?
2583 //
2584 if (::tilemap[tilex][tiley] != 0 &&
2585 (::tilemap[tilex][tiley] & 0xC0) == 0)
2586 {
2587 return 127;
2588 }
2589
2590 // Get initial areanumber from map
2591 //
2592 auto offset = ::farmapylookup[tiley] + tilex;
2593 auto areanumber = ::ValidAreaTile(::mapsegs[0] + offset);
2594
2595 // Special tile areas must use a valid areanumber tile around it.
2596 //
2597 if (areanumber == 0) {
2598 auto found = false;
2599
2600 for (auto i = 0; i < 8; ++i) {
2601 auto new_x = tilex + ::xy_offset[i][0];
2602
2603 if (new_x < 0 || new_x >= MAPSIZE) {
2604 continue;
2605 }
2606
2607
2608 auto new_y = tiley + ::xy_offset[i][1];
2609
2610 if (new_y < 0 || new_y >= MAPSIZE) {
2611 continue;
2612 }
2613
2614 offset = ::farmapylookup[new_y] + new_x;
2615 areanumber = ::ValidAreaTile(::mapsegs[0] + offset);
2616
2617 if (areanumber != 0) {
2618 found = true;
2619 break;
2620 }
2621 }
2622
2623 if (!found) {
2624 areanumber = AREATILE;
2625 }
2626 }
2627
2628 // Merge hidden areanumbers into non-hidden areanumbers AND pull all
2629 // values down to an indexable range.
2630 //
2631 if (areanumber >= HIDDENAREATILE) {
2632 ::GAN_HiddenArea = true;
2633 areanumber -= HIDDENAREATILE;
2634 } else {
2635 areanumber -= AREATILE;
2636 }
2637
2638 return areanumber;
2639 }
2640
ValidAreaTile(const uint16_t * ptr)2641 uint8_t ValidAreaTile(
2642 const uint16_t* ptr)
2643 {
2644 switch (*ptr) {
2645 case AREATILE:
2646 case HIDDENAREATILE:
2647 case DOORTRIGGERTILE:
2648 case WINTIGGERTILE:
2649 case SMART_ON_TRIGGER:
2650 case SMART_OFF_TRIGGER:
2651 case AMBUSHTILE:
2652 case LINC_TILE:
2653 case CLOAK_AMBUSH_TILE:
2654 break;
2655
2656 default:
2657 if (*ptr > AREATILE) {
2658 return static_cast<uint8_t>(*ptr);
2659 }
2660 break;
2661 }
2662
2663 return 0;
2664 }
2665
2666
2667 /*
2668 =============================================================================
2669
2670 ACTIONS
2671
2672 =============================================================================
2673 */
2674
Cmd_Fire()2675 void Cmd_Fire()
2676 {
2677 if (noShots) {
2678 return;
2679 }
2680
2681 if ((gamestate.weapon == wp_autocharge) && (gamestate.weapon_wait)) {
2682 return;
2683 }
2684
2685 buttonheld[bt_attack] = true;
2686
2687 gamestate.weaponframe = 0;
2688
2689 player->state = &s_attack;
2690
2691 gamestate.attackframe = 0;
2692 gamestate.attackcount = attackinfo[static_cast<int>(gamestate.weapon)][gamestate.attackframe].tics;
2693 gamestate.weaponframe = attackinfo[static_cast<int>(gamestate.weapon)][gamestate.attackframe].frame;
2694 }
2695
Cmd_Use(bool & play_hit_wall_sound)2696 void Cmd_Use(
2697 bool& play_hit_wall_sound)
2698 {
2699 play_hit_wall_sound = false;
2700
2701
2702 int16_t checkx;
2703 int16_t checky;
2704 int16_t door_index;
2705 int16_t dir;
2706 uint16_t iconnum;
2707 uint8_t static interrogate_delay = 0;
2708
2709 bool tryDetonator = false;
2710
2711 // Find which cardinal direction the player is facing
2712 //
2713 if (player->angle < ANGLES / 8 || player->angle > 7 * ANGLES / 8) {
2714 checkx = player->tilex + 1;
2715 checky = player->tiley;
2716 dir = di_east;
2717 } else if (player->angle < 3 * ANGLES / 8) {
2718 checkx = player->tilex;
2719 checky = player->tiley - 1;
2720 dir = di_north;
2721 } else if (player->angle < 5 * ANGLES / 8) {
2722 checkx = player->tilex - 1;
2723 checky = player->tiley;
2724 dir = di_west;
2725 } else {
2726 checkx = player->tilex;
2727 checky = player->tiley + 1;
2728 dir = di_south;
2729 }
2730
2731 door_index = tilemap[checkx][checky];
2732 iconnum = *(mapsegs[1] + farmapylookup[checky] + checkx);
2733
2734 // BBi Play sound only for walls
2735 if (door_index != 0 && (door_index & 0x80) == 0) {
2736 play_hit_wall_sound = true;
2737 }
2738
2739 // Test for a pushable wall
2740 //
2741 if (iconnum == PUSHABLETILE) {
2742 PushWall(checkx, checky, dir);
2743 } else if (!buttonheld[bt_use]) {
2744 // Test for doors / elevator
2745 //
2746 if ((door_index & 0xC0) == 0x80) {
2747 buttonheld[bt_use] = true;
2748 OperateDoor(door_index & ~0x80);
2749 } else {
2750 // Test for special tile types...
2751 //
2752 switch (door_index & 63) {
2753 // Test for 'display elevator buttons'
2754 //
2755 case TRANSPORTERTILE: {
2756 int16_t new_floor;
2757
2758 if ((new_floor = InputFloor()) != -1 && new_floor != gamestate.mapon) {
2759 int16_t angle = player->angle;
2760
2761 gamestuff.level[gamestate.mapon].ptilex = player->tilex;
2762 gamestuff.level[gamestate.mapon].ptiley = player->tiley;
2763
2764 if (::is_ps()) {
2765 angle = player->angle - 180;
2766 if (angle < 0) {
2767 angle += ANGLES;
2768 }
2769 }
2770
2771 gamestuff.level[gamestate.mapon].pangle = angle;
2772 playstate = ::is_ps() ? ex_transported : ex_warped;
2773
2774 gamestate.lastmapon = gamestate.mapon;
2775 gamestate.mapon = new_floor - 1;
2776 } else {
2777 DrawPlayScreen(false);
2778 }
2779 }
2780 break;
2781
2782 case DIRECTTRANSPORTTILE:
2783 switch (iconnum & 0xff00) {
2784 case 0xf400:
2785 playstate = ex_transported;
2786 gamestate.lastmapon = gamestate.mapon;
2787 gamestate.mapon = (iconnum & 0xff) - 1;
2788
2789 if (::is_aog()) {
2790 gamestuff.level[gamestate.mapon + 1].ptilex = player->tilex;
2791 gamestuff.level[gamestate.mapon + 1].ptiley = player->tiley;
2792
2793 #if 0
2794 {
2795 int angle = player->angle - 180;
2796
2797 while (angle < 0) {
2798 angle += ANGLES;
2799 }
2800
2801 gamestuff.level[gamestate.mapon + 1].pangle =
2802 static_cast<int16_t>(angle);
2803 }
2804 #else
2805 gamestuff.level[gamestate.mapon + 1].pangle = 0;
2806 #endif
2807 }
2808 break;
2809
2810 default:
2811 // Stay in current level warp to new location
2812
2813 playstate = ex_transported;
2814 Warped();
2815 playstate = ex_stillplaying;
2816
2817 player->tilex = (iconnum >> 8);
2818 player->tiley = iconnum & 0xff;
2819 player->x = ((int32_t)player->tilex << TILESHIFT) + TILEGLOBAL / 2;
2820 player->y = ((int32_t)player->tiley << TILESHIFT) + TILEGLOBAL / 2;
2821
2822 DrawWarpIn();
2823 break;
2824 }
2825 break;
2826
2827 //
2828 // Test for Wall Switch Activation
2829 //
2830 case OFF_SWITCH:
2831 case ON_SWITCH:
2832 ActivateWallSwitch(iconnum, checkx, checky);
2833 break;
2834
2835
2836 // Test for Concession Machines
2837 //
2838
2839 case FOODTILE:
2840 case SODATILE:
2841 OperateConcession(static_cast<uint16_t>(reinterpret_cast<size_t>(actorat[checkx][checky])));
2842 break;
2843
2844 default:
2845 if (::is_ps()) {
2846 tryDetonator = true;
2847 }
2848 break;
2849 }
2850 }
2851 } else if (!interrogate_delay) {
2852 const int INTERROGATEDIST = MINACTORDIST;
2853 const int8_t MDIST = 2;
2854 const int16_t INTG_ANGLE = 45;
2855
2856 int8_t x, y;
2857 objtype* intg_ob = nullptr, * ob;
2858 int32_t dx, dy, dist, intg_dist = INTERROGATEDIST + 1;
2859
2860 for (y = -MDIST; y < MDIST + 1; y++) {
2861 for (x = -MDIST; x < MDIST + 1; x++) {
2862 auto dst_x = ::player->tilex + x;
2863 auto dst_y = ::player->tiley + y;
2864
2865 // Don't check outside of the map plane:
2866 if (dst_x < 0 || dst_y < 0 || dst_x > 63 || dst_y > 63) {
2867 continue;
2868 }
2869
2870 if ((!::tilemap[dst_x][dst_y]) &&
2871 (::actorat[dst_x][dst_y] >= ::objlist))
2872 {
2873 ob = ::actorat[dst_x][dst_y];
2874 } else {
2875 continue;
2876 }
2877 dx = player->x - ob->x;
2878 dx = LABS(dx);
2879 dy = player->y - ob->y;
2880 dy = LABS(dy);
2881 dist = dx < dy ? dx : dy;
2882 if ((ob->obclass == gen_scientistobj) &&
2883 ((ob->flags & (FL_FRIENDLY | FL_VISABLE)) == (FL_FRIENDLY | FL_VISABLE)) &&
2884 (dist < intg_dist))
2885 {
2886 if ((ob->flags & FL_ATTACKMODE) != 0) {
2887 ob->flags &= ~(FL_FRIENDLY | FL_INFORMANT);
2888 } else {
2889 int16_t angle = CalcAngle(player, ob);
2890
2891 angle = ABS(player->angle - angle);
2892 if (angle > INTG_ANGLE / 2) {
2893 continue;
2894 }
2895
2896 intg_ob = ob;
2897 intg_dist = dist;
2898 }
2899 }
2900 }
2901 }
2902
2903 if (intg_ob) {
2904 if (Interrogate(intg_ob)) {
2905 interrogate_delay = 20; // Informants have 1/3 sec delay
2906 } else {
2907 interrogate_delay = 120; // Non-informants have 2 sec delay
2908 }
2909 } else if (::is_ps()) {
2910 tryDetonator = true;
2911 }
2912 } else {
2913 if (tics < interrogate_delay) {
2914 interrogate_delay -= static_cast<uint8_t>(tics);
2915 } else {
2916 interrogate_delay = 0;
2917 }
2918
2919 if (::is_ps()) {
2920 tryDetonator = true;
2921 }
2922 }
2923
2924 if (::is_ps()) {
2925 if (tryDetonator) {
2926 if ((!tryDetonatorDelay) && gamestate.plasma_detonators) {
2927 TryDropPlasmaDetonator();
2928 tryDetonatorDelay = 60;
2929 }
2930 } else {
2931 tryDetonatorDelay = 60;
2932 }
2933 }
2934
2935 if (!buttonheld[bt_use]) {
2936 interrogate_delay = 0;
2937 }
2938 }
2939
2940
2941 // ==========================================================================
2942 //
2943 // INTERROGATE CODE
2944 //
2945 // ==========================================================================
2946
2947 const int MSG_BUFFER_LEN = 150;
2948
2949 char msg[MSG_BUFFER_LEN + 1];
2950
2951 char* InfAreaMsgs[MAX_INF_AREA_MSGS];
2952 uint8_t NumAreaMsgs, LastInfArea;
2953 int16_t FirstGenInfMsg, TotalGenInfMsgs;
2954
2955 scientist_t InfHintList; // Informant messages
2956 scientist_t NiceSciList; // Non-informant, non-pissed messages
2957 scientist_t MeanSciList; // Non-informant, pissed messages
2958
2959 char int_interrogate[] = "INTERROGATE:",
2960 int_informant[] = " ^FC3aINFORMANT^FCa6",
2961 int_rr[] = "\r\r",
2962 int_xx[] = "^XX",
2963 int_haveammo[] = " HEY BLAKE,\r TAKE MY CHARGE PACK!",
2964 int_havetoken[] = " HEY BLAKE,\r TAKE MY FOOD TOKENS!";
2965
2966
Interrogate(objtype * ob)2967 bool Interrogate(
2968 objtype* ob)
2969 {
2970 bool rt_value = true;
2971 char* msgptr = nullptr;
2972
2973 strcpy(msg, int_interrogate);
2974
2975 if (ob->flags & FL_INFORMANT) { // Informant
2976 strcat(msg, int_informant);
2977
2978 if (ob->flags & FL_INTERROGATED) {
2979 if ((ob->flags & FL_HAS_AMMO) && (gamestate.ammo != MAX_AMMO)) {
2980 GiveAmmo((US_RndT() % 8) + 1);
2981 ob->flags &= ~FL_HAS_AMMO;
2982 msgptr = int_haveammo;
2983 } else if ((ob->flags & FL_HAS_TOKENS) && (gamestate.tokens != MAX_TOKENS)) {
2984 GiveToken(5);
2985 ob->flags &= ~FL_HAS_TOKENS;
2986 msgptr = int_havetoken;
2987 }
2988 }
2989
2990 if (!msgptr) {
2991 // If new areanumber OR no 'area msgs' have been compiled, compile
2992 // a list of all special messages for this areanumber.
2993 //
2994 if ((LastInfArea == 0xFF) || (LastInfArea != ob->areanumber)) {
2995 NumAreaMsgs = 0;
2996
2997 for (auto i = 0; i < InfHintList.NumMsgs; ++i) {
2998 const auto& ci = InfHintList.smInfo[i];
2999
3000 if (ci.areanumber == 0xFF) {
3001 break;
3002 }
3003
3004 if (ci.areanumber == ob->areanumber) {
3005 InfAreaMsgs[NumAreaMsgs++] = InfHintList.smInfo[ci.mInfo.local_val].mInfo.mSeg;
3006 }
3007 }
3008
3009 LastInfArea = ob->areanumber;
3010 }
3011
3012 // Randomly select an informant hint, either: specific to areanumber
3013 // or general hint...
3014 //
3015 if (NumAreaMsgs) {
3016 if (ob->ammo != ob->areanumber) {
3017 ob->s_tilex = 0xFF;
3018 }
3019 ob->ammo = ob->areanumber;
3020 if (ob->s_tilex == 0xFF) {
3021 ob->s_tilex = static_cast<uint8_t>(Random(NumAreaMsgs));
3022 }
3023 msgptr = InfAreaMsgs[ob->s_tilex];
3024 } else {
3025 if (ob->s_tiley == 0xff) {
3026 ob->s_tiley = static_cast<uint8_t>(FirstGenInfMsg + Random(TotalGenInfMsgs));
3027 }
3028 msgptr = InfHintList.smInfo[ob->s_tiley].mInfo.mSeg;
3029 }
3030
3031 // Still no msgptr? This is a shared message! Use smInfo[local_val]
3032 // for this message.
3033 //
3034 if (!msgptr) {
3035 msgptr = InfHintList.smInfo[InfHintList.smInfo[ob->s_tiley].mInfo.local_val].mInfo.mSeg;
3036 }
3037
3038 ob->flags |= FL_INTERROGATED; // Scientist has been interrogated
3039 }
3040 } else { // Non-Informant
3041 scientist_t* st;
3042
3043 rt_value = false;
3044 if ((ob->flags & FL_MUST_ATTACK) || (US_RndT() & 1)) { // Mean
3045 ob->flags &= ~FL_FRIENDLY; // Make him attack!
3046 ob->flags |= FL_INTERROGATED; // " " "
3047 st = &MeanSciList;
3048 } else { // Nice
3049 ob->flags |= FL_MUST_ATTACK; // Make him mean!
3050 st = &NiceSciList;
3051 }
3052
3053 msgptr = st->smInfo[Random(st->NumMsgs)].mInfo.mSeg;
3054 }
3055
3056 if (msgptr) {
3057 strcat(msg, int_rr);
3058 strcat(msg, msgptr);
3059 strcat(msg, int_xx);
3060 if (strlen(msg) > MSG_BUFFER_LEN) {
3061 ::Quit("Interrogation message too long.");
3062 }
3063 DisplayInfoMsg(msg, MP_INTERROGATE, DISPLAY_MSG_STD_TIME * 2, MT_GENERAL);
3064
3065 ::sd_play_player_sound(INTERROGATESND, bstone::AC_INTERROGATION);
3066 }
3067
3068 return rt_value;
3069 }
3070
3071
3072 // ==========================================================================
3073 //
3074 // ELEVATOR CODE
3075 //
3076 // ==========================================================================
3077
3078 char if_help[] = "UP/DN MOVES SELECTOR - ENTER ACTIVATES";
3079 char if_noImage[] = " AREA\n"
3080 " UNMAPPED\n"
3081 "\n"
3082 "\n"
3083 " PRESS ENTER\n"
3084 " TO TELEPORT";
3085
3086 statsInfoType ov_stats;
3087 static VgaBuffer ov_buffer;
3088 bool ov_noImage = false;
3089
3090 const int TOV_X = 16;
3091 const int TOV_Y = 132;
3092
3093
InputFloor()3094 int16_t InputFloor()
3095 {
3096 if (::is_aog()) {
3097 const std::string messages[4] = {
3098 // "Current floor:\nSelect a floor."
3099 ::ca_load_script(ELEVMSG0_TEXT),
3100 // "RED access card used to unlock floor!"
3101 ::ca_load_script(ELEVMSG1_TEXT),
3102 // "Floor is locked. Try another floor."
3103 ::ca_load_script(ELEVMSG4_TEXT),
3104 // "You must first get the red access keycard!"
3105 ::ca_load_script(ELEVMSG5_TEXT)
3106 }; // messages
3107
3108 const char* const floor_number_strings[10] = {
3109 "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"
3110 };
3111
3112 ::CA_CacheGrChunk(STARTFONT + 3);
3113 ::CacheLump(TELEPORT_LUMP_START, TELEPORT_LUMP_END);
3114
3115 ::VW_FadeOut();
3116
3117 ::DrawTopInfo(sp_normal);
3118
3119 auto border_width = 7;
3120 auto border_height = 5;
3121 auto outer_height = ::ref_view_height;
3122
3123 ::BevelBox(
3124 0,
3125 ::ref_top_bar_height,
3126 static_cast<int16_t>(::vga_ref_width),
3127 static_cast<int16_t>(outer_height),
3128 BORDER_HI_COLOR,
3129 BORDER_MED_COLOR,
3130 BORDER_LO_COLOR);
3131
3132 ::BevelBox(
3133 static_cast<int16_t>(border_width),
3134 static_cast<int16_t>(::ref_top_bar_height + border_height),
3135 static_cast<int16_t>(::vga_ref_width - (2 * border_width)),
3136 static_cast<int16_t>(outer_height - (2 * border_height)),
3137 BORDER_LO_COLOR,
3138 BORDER_MED_COLOR,
3139 BORDER_HI_COLOR);
3140
3141 ::CacheDrawPic(8, ref_top_bar_height + 6, TELEPORTBACKPIC);
3142
3143 ::fontnumber = 1;
3144 ::CA_CacheGrChunk(STARTFONT + 1);
3145 ::CacheBMAmsg(FLOORMSG_TEXT);
3146 ::UNCACHEGRCHUNK(STARTFONT + 1);
3147
3148 ::ShowOverhead(
3149 14,
3150 ref_top_bar_height + 55,
3151 32,
3152 0,
3153 OV_KEYS | OV_WHOLE_MAP);
3154
3155 ::IN_ClearKeysDown();
3156
3157 auto result = -2;
3158 auto draw_stats = true;
3159 auto draw_message = true;
3160 auto draw_current_floor = true;
3161 auto draw_locked_floor = false;
3162 auto use_delay = false;
3163 auto draw_button = false;
3164 auto button_index = 0;
3165 auto is_button_pressed = false;
3166 auto message = &messages[0];
3167
3168 PresenterInfo pi {};
3169 pi.xl = 24;
3170 pi.yl = ref_top_bar_height + 8;
3171 pi.xh = pi.xl + 210;
3172 pi.yh = pi.yl + 34;
3173 pi.fontnumber = 3;
3174 pi.custom_line_height = 17;
3175
3176 ::fontcolor = 0x38;
3177
3178 auto last_unlocked_map = 0;
3179
3180 for (int i = 1; i < MAPS_WITH_STATS; ++i) {
3181 if (!::gamestuff.level[i].locked) {
3182 last_unlocked_map = i;
3183 }
3184 }
3185
3186 while (result == -2) {
3187 ::CalcTics();
3188 ::in_handle_events();
3189
3190 if (::Keyboard[ScanCode::sc_escape]) {
3191 result = -1;
3192 }
3193
3194 auto target_level = 0;
3195
3196 for (auto i = static_cast<int>(ScanCode::sc_1);
3197 i <= static_cast<int>(ScanCode::sc_0);
3198 ++i)
3199 {
3200 if (::Keyboard[i]) {
3201 target_level = i - static_cast<int>(ScanCode::sc_1) + 1;
3202 break;
3203 }
3204 }
3205
3206 if (target_level >= 1 && target_level != ::gamestate.mapon) {
3207 ::sd_play_player_sound(ELEV_BUTTONSND, bstone::AC_ITEM);
3208
3209 draw_button = true;
3210 is_button_pressed = true;
3211 button_index = target_level - 1;
3212
3213 if (!::gamestuff.level[target_level].locked) {
3214 result = target_level;
3215 } else if (::gamestate.numkeys[kt_red] > 0 &&
3216 target_level == (last_unlocked_map + 1))
3217 {
3218 result = target_level;
3219
3220 use_delay = true;
3221 draw_message = true;
3222 draw_current_floor = false;
3223 message = &messages[1];
3224
3225 ::gamestate.numkeys[kt_red] = 0;
3226 } else {
3227 use_delay = true;
3228 draw_message = true;
3229 draw_current_floor = false;
3230
3231 if (target_level == (last_unlocked_map + 1)) {
3232 draw_locked_floor = false;
3233 message = &messages[3];
3234 } else {
3235 draw_locked_floor = true;
3236 message = &messages[2];
3237 }
3238 }
3239 }
3240
3241 if (draw_message) {
3242 draw_message = false;
3243
3244 ::VWB_DrawPic(24, ref_top_bar_height + 10, TELEPORT_TEXT_BG);
3245
3246 ::fontcolor = 0x97;
3247 pi.script[0] = message->c_str();
3248 pi.flags = TPF_CACHE_NO_GFX | TPF_USE_CURRENT;
3249 pi.cur_x = 0xFFFF;
3250 pi.cur_y = 0xFFFF;
3251 ::TP_InitScript(&pi);
3252 ::TP_Presenter(&pi);
3253
3254 if (draw_current_floor) {
3255 ::fontnumber = 3;
3256 ::fontcolor = 0x38;
3257
3258 ::px = 167;
3259 ::py = ref_top_bar_height + 10;
3260
3261 ::USL_DrawString(
3262 floor_number_strings[::gamestate.mapon - 1]);
3263 }
3264
3265 if (draw_locked_floor) {
3266 ::fontnumber = 3;
3267 ::fontcolor = 0x38;
3268
3269 ::px = 82;
3270 ::py = ref_top_bar_height + 10;
3271
3272 ::USL_DrawString(
3273 floor_number_strings[target_level - 1]);
3274 }
3275
3276 if (draw_button) {
3277 draw_button = false;
3278
3279 auto base_x = 264;
3280 auto base_y = ref_top_bar_height + 98;
3281 auto step_x = 24;
3282 auto step_y = 20;
3283
3284 auto x = base_x + (step_x * (button_index % 2));
3285 auto y = base_y - (step_y * (button_index / 2));
3286
3287 auto base_pic =
3288 is_button_pressed ?
3289 TELEPORT1ONPIC :
3290 TELEPORT1OFFPIC;
3291
3292 ::VWB_DrawPic(
3293 x,
3294 y,
3295 base_pic + button_index);
3296 }
3297 }
3298
3299 ::CycleColors();
3300 ::VW_UpdateScreen();
3301
3302 if (::screenfaded) {
3303 ::VW_FadeIn();
3304 }
3305
3306 if (draw_stats) {
3307 draw_stats = false;
3308
3309 static_cast<void>(::ShowStats(
3310 167,
3311 ref_top_bar_height + 76,
3312 ss_normal,
3313 &::gamestuff.level[::gamestate.mapon].stats));
3314
3315 ::IN_ClearKeysDown();
3316 }
3317
3318 if (use_delay) {
3319 use_delay = false;
3320 draw_message = true;
3321 draw_current_floor = true;
3322 draw_locked_floor = false;
3323 draw_button = true;
3324 is_button_pressed = false;
3325 message = &messages[0];
3326
3327 ::IN_UserInput(210);
3328 ::IN_ClearKeysDown();
3329 }
3330 }
3331
3332 ::IN_ClearKeysDown();
3333
3334 return static_cast<int16_t>(result);
3335 } else {
3336 const auto RADAR_FLAGS = OV_KEYS;
3337 const auto MAX_TELEPORTS = 20;
3338 const int8_t MAX_MOVE_DELAY = 10;
3339
3340 int16_t buttonPic, buttonY;
3341 int16_t rt_code = -2, tpNum = gamestate.mapon, lastTpNum = tpNum;
3342 int16_t teleX[MAX_TELEPORTS] = { 16, 40, 86, 23, 44, 62, 83, 27, 118, 161, 161, 161, 213, 213, 184, 205, 226, 256, 276, 276 };
3343 int16_t teleY[MAX_TELEPORTS] = { 13, 26, 9, 50, 50, 50, 50, 62, 42, 17, 26, 35, 41, 50, 62, 62, 62, 10, 10, 30 };
3344 int8_t moveActive = 0;
3345 objtype old_player;
3346 bool locked = false;
3347 bool buttonsDrawn = false;
3348
3349 ClearMemory();
3350 VW_FadeOut();
3351
3352 CacheDrawPic(0, 0, TELEPORTBACKTOPPIC);
3353 CacheDrawPic(0, 12 * 8, TELEPORTBACKBOTPIC);
3354 DisplayTeleportName(static_cast<int8_t>(tpNum), locked);
3355 CacheLump(TELEPORT_LUMP_START, TELEPORT_LUMP_END);
3356 VWB_DrawMPic(teleX[tpNum], teleY[tpNum], TELEPORT1ONPIC + tpNum);
3357
3358 memcpy(&old_player, player, sizeof(objtype));
3359 player->angle = 90;
3360 player->x = player->y = ((int32_t)32 << TILESHIFT) + (TILEGLOBAL / 2);
3361
3362 ov_buffer.resize(4096);
3363
3364 ShowStats(0, 0, ss_justcalc, &gamestuff.level[gamestate.mapon].stats);
3365 memcpy(&ov_stats, &gamestuff.level[gamestate.mapon].stats, sizeof(statsInfoType));
3366 ShowOverhead(TOV_X, TOV_Y, 32, 0, RADAR_FLAGS);
3367 SaveOverheadChunk(tpNum);
3368
3369 px = 115;
3370 py = 188;
3371 fontcolor = 0xaf;
3372 fontnumber = 2;
3373 ShPrint(if_help, 0, false);
3374
3375 controlx = controly = 0;
3376 IN_ClearKeysDown();
3377 while (rt_code == -2) {
3378 CalcTics();
3379
3380 // BBi
3381 ::in_handle_events();
3382
3383 if (Keyboard[ScanCode::sc_left_arrow]) {
3384 controlx = -1;
3385 } else if (Keyboard[ScanCode::sc_right_arrow]) {
3386 controlx = 1;
3387 } else {
3388 controlx = 0;
3389 }
3390
3391 if (Keyboard[ScanCode::sc_up_arrow]) {
3392 controly = -1;
3393 } else if (Keyboard[ScanCode::sc_down_arrow]) {
3394 controly = 1;
3395 } else {
3396 controly = 0;
3397 }
3398
3399 if (Keyboard[ScanCode::sc_escape] || buttonstate[bt_strafe]) {
3400 rt_code = -1; // ABORT
3401
3402 LoadLocationText(static_cast<int16_t>(
3403 gamestate.mapon + MAPS_PER_EPISODE * gamestate.episode));
3404 break;
3405 } else if (Keyboard[ScanCode::sc_return] || buttonstate[bt_attack]) {
3406 if (locked) {
3407 if (!::sd_is_player_channel_playing(bstone::AC_NO_WAY)) {
3408 ::sd_play_player_sound(NOWAYSND, bstone::AC_NO_WAY);
3409 }
3410 } else {
3411 int8_t loop;
3412
3413 rt_code = tpNum; // ACCEPT
3414
3415 // Flash selection
3416 //
3417 for (loop = 0; loop < 10; loop++) {
3418 VWB_DrawMPic(teleX[tpNum], teleY[tpNum], TELEPORT1OFFPIC + tpNum);
3419 VW_UpdateScreen();
3420 VW_WaitVBL(4);
3421
3422 VWB_DrawMPic(teleX[tpNum], teleY[tpNum], TELEPORT1ONPIC + tpNum);
3423 VW_UpdateScreen();
3424 VW_WaitVBL(4);
3425 }
3426
3427 break;
3428 }
3429 }
3430
3431 CheckMusicToggle();
3432
3433 // Handle delay
3434 //
3435 if (moveActive) {
3436 moveActive -= static_cast<int8_t>(tics);
3437 if (moveActive < 0) {
3438 moveActive = 0;
3439 }
3440 }
3441
3442 // Move to NEXT / PREV teleport?
3443 //
3444 buttonY = 0;
3445 if (controlx > 0 || controly > 0) {
3446 if (!moveActive && tpNum < MAX_TELEPORTS - 1) {
3447 tpNum++; // MOVE NEXT
3448 moveActive = MAX_MOVE_DELAY;
3449 }
3450
3451 buttonPic = TELEDNONPIC;
3452 buttonY = 104;
3453 } else if (controlx < 0 || controly < 0) {
3454 if (!moveActive && tpNum) {
3455 tpNum--; // MOVE PREV
3456 moveActive = MAX_MOVE_DELAY;
3457 }
3458
3459 buttonPic = TELEUPONPIC;
3460 buttonY = 91;
3461 }
3462
3463 // Light buttons?
3464 //
3465 if (buttonY) {
3466 VWB_DrawMPic(34, 91, TELEUPOFFPIC);
3467 VWB_DrawMPic(270, 91, TELEUPOFFPIC);
3468 VWB_DrawMPic(34, 104, TELEDNOFFPIC);
3469 VWB_DrawMPic(270, 104, TELEDNOFFPIC);
3470
3471 VWB_DrawMPic(34, buttonY, buttonPic);
3472 VWB_DrawMPic(270, buttonY, buttonPic);
3473 buttonsDrawn = true;
3474 } else
3475 // Unlight buttons?
3476 //
3477 if (buttonsDrawn) {
3478 VWB_DrawMPic(34, 91, TELEUPOFFPIC);
3479 VWB_DrawMPic(270, 91, TELEUPOFFPIC);
3480 VWB_DrawMPic(34, 104, TELEDNOFFPIC);
3481 VWB_DrawMPic(270, 104, TELEDNOFFPIC);
3482 buttonsDrawn = false;
3483 }
3484
3485 // Change visual information
3486 //
3487 if (tpNum != lastTpNum) {
3488 locked = gamestuff.level[tpNum].locked;
3489 DisplayTeleportName(static_cast<int8_t>(tpNum), locked);
3490
3491 VWB_DrawMPic(teleX[lastTpNum], teleY[lastTpNum], TELEPORT1OFFPIC + lastTpNum);
3492 VWB_DrawMPic(teleX[tpNum], teleY[tpNum], TELEPORT1ONPIC + tpNum);
3493
3494 LoadOverheadChunk(tpNum);
3495 ShowOverheadChunk();
3496 if (ov_noImage) {
3497 fontcolor = 0x57;
3498 WindowX = WindowW = TOV_X;
3499 WindowY = WindowH = TOV_Y;
3500 WindowW += 63;
3501 WindowH += 63;
3502 PrintX = TOV_X + 5;
3503 PrintY = TOV_Y + 13;
3504 US_Print(if_noImage);
3505 }
3506 lastTpNum = tpNum;
3507 }
3508
3509 if (locked) {
3510 ShowOverhead(TOV_X, TOV_Y, 32, -1, RADAR_FLAGS);
3511 }
3512
3513 CycleColors();
3514 VW_UpdateScreen();
3515 if (screenfaded) {
3516 VW_FadeIn();
3517 ShowStats(235, 138, ss_normal, &ov_stats);
3518 IN_ClearKeysDown();
3519 controlx = controly = 0;
3520 }
3521 }
3522
3523 VW_FadeOut();
3524
3525 memcpy(player, &old_player, sizeof(objtype));
3526 UnCacheLump(TELEPORT_LUMP_START, TELEPORT_LUMP_END);
3527
3528 DrawPlayScreen(false);
3529 IN_ClearKeysDown();
3530
3531 return rt_code;
3532 }
3533 }
3534
ShowOverheadChunk()3535 void ShowOverheadChunk()
3536 {
3537 VL_MemToScreen(ov_buffer.data(), 64, 64, TOV_X, TOV_Y);
3538 ShowStats(235, 138, ss_quick, &ov_stats);
3539 }
3540
LoadOverheadChunk(int tpNum)3541 void LoadOverheadChunk(
3542 int tpNum)
3543 {
3544 // Find and load chunk
3545 //
3546 ::g_playtemp.set_position(0);
3547
3548 std::string chunk_name = "OV" + (
3549 bstone::FormatString() << std::setw(2) << std::setfill('0') <<
3550 std::hex << std::uppercase << tpNum).to_string();
3551
3552 bool is_succeed = true;
3553 bstone::Crc32 checksum;
3554 bstone::BinaryReader reader(&g_playtemp);
3555
3556 if (::FindChunk(&g_playtemp, chunk_name) > 0) {
3557 try {
3558 ::deserialize_field(
3559 reinterpret_cast<uint8_t(&)[4096]>(ov_buffer[0]),
3560 reader, checksum);
3561 } catch (const ArchiveException&) {
3562 is_succeed = false;
3563 }
3564
3565 ov_stats.deserialize(reader, checksum);
3566
3567 uint32_t saved_checksum = 0;
3568 is_succeed &= reader.read(saved_checksum);
3569 bstone::Endian::lei(saved_checksum);
3570 is_succeed &= (saved_checksum == checksum.get_value());
3571 } else {
3572 is_succeed = false;
3573 }
3574
3575 if (!is_succeed) {
3576 std::uninitialized_fill(
3577 ov_buffer.begin(),
3578 ov_buffer.end(),
3579 0x52);
3580
3581 std::uninitialized_fill_n(
3582 reinterpret_cast<uint8_t*>(&ov_stats),
3583 sizeof(statsInfoType),
3584 0);
3585 }
3586
3587 ov_noImage = !is_succeed;
3588 }
3589
SaveOverheadChunk(int tpNum)3590 void SaveOverheadChunk(
3591 int tpNum)
3592 {
3593 // Remove level chunk from file
3594 //
3595 std::string chunk_name = "OV" + (
3596 bstone::FormatString() << std::setw(2) << std::setfill('0') <<
3597 std::hex << std::uppercase << tpNum).to_string();
3598
3599 ::DeleteChunk(g_playtemp, chunk_name);
3600
3601 // Prepare buffer
3602 //
3603 ::VL_ScreenToMem(ov_buffer.data(), 64, 64, TOV_X, TOV_Y);
3604
3605 bstone::Crc32 checksum;
3606 bstone::BinaryWriter writer(&g_playtemp);
3607
3608 // Write chunk ID, SIZE, and IMAGE
3609 //
3610 g_playtemp.seek(0, bstone::StreamSeekOrigin::end);
3611 g_playtemp.write(chunk_name.c_str(), 4);
3612 g_playtemp.skip(4);
3613
3614 int64_t beg_offset = g_playtemp.get_position();
3615
3616 ::serialize_field(
3617 reinterpret_cast<const uint8_t(&)[4096]>(ov_buffer[0]),
3618 writer, checksum);
3619 ov_stats.serialize(writer, checksum);
3620 writer.write(bstone::Endian::le(checksum.get_value()));
3621
3622 int64_t end_offset = g_playtemp.get_position();
3623 int32_t chunk_size = static_cast<int32_t>(end_offset - beg_offset);
3624 g_playtemp.seek(-(chunk_size + 4), bstone::StreamSeekOrigin::current);
3625 writer.write(bstone::Endian::le(chunk_size));
3626 }
3627
DisplayTeleportName(int8_t tpNum,bool locked)3628 void DisplayTeleportName(
3629 int8_t tpNum,
3630 bool locked)
3631 {
3632 const char* s;
3633 int w;
3634 int h;
3635
3636 if (locked) {
3637 fontcolor = 0xf5;
3638 s = "-- TELEPORT DISABLED --";
3639 } else {
3640 fontcolor = 0x57;
3641 LoadLocationText(tpNum);
3642 s = LocationText;
3643 }
3644 VW_MeasurePropString(s, &w, &h);
3645 py = 103;
3646 px = static_cast<int16_t>(160 - w / 2);
3647 VW_Bar(54, 101, 212, 9, 0x52);
3648 ShPrint(s, 0, false);
3649 }
3650
CacheDrawPic(int x,int y,int pic)3651 void CacheDrawPic(
3652 int x,
3653 int y,
3654 int pic)
3655 {
3656 CA_CacheGrChunk(static_cast<int16_t>(pic));
3657 VWB_DrawPic(x, y, pic);
3658 UNCACHEGRCHUNK(static_cast<uint16_t>(pic));
3659 }
3660
3661
3662 // ===========================================================================
3663 //
3664 // MISSION STATISTICS CODE
3665 //
3666 // ===========================================================================
3667
3668 const int BAR_W = 48;
3669 const int BAR_H = 5;
3670
3671 const int PERC_W = 13;
3672 const int PERC_H = 5;
3673
3674 bool show_stats_quick;
3675
3676
ShowStats(int16_t bx,int16_t by,ss_type type,statsInfoType * stats)3677 int16_t ShowStats(
3678 int16_t bx,
3679 int16_t by,
3680 ss_type type,
3681 statsInfoType* stats)
3682 {
3683 int16_t floor, total = 0, mission = 0, p1, p2, p3, loop, maxPerFloor;
3684
3685 // Define max points per floor...
3686 //
3687 if (stats->total_points || stats->total_inf || stats->total_enemy) {
3688 maxPerFloor = 300;
3689 } else {
3690 maxPerFloor = 0;
3691 }
3692
3693 // Setup to test for bypassing stats.
3694 //
3695 LastScan = ScanCode::sc_none;
3696
3697 if (type == ss_quick) {
3698 show_stats_quick = true;
3699 } else {
3700 show_stats_quick = false;
3701 }
3702
3703 // Show ratio for each statistic:
3704 //
3705 // TOTAL POINTS, INFORMANTS ALIVE, ENEMY DESTROYED,
3706 // OVERALL FLOOR, OVERALL MISSION
3707 //
3708
3709 // Show TOTAL POINTS ratio.
3710 //
3711 p1 = ShowRatio(bx, by, bx + 52, by, stats->total_points, stats->accum_points, type);
3712
3713 // Show INFORMANTS ALIVE ratio.
3714 //
3715 by += 7;
3716 p2 = ShowRatio(bx, by, bx + 52, by, stats->total_inf, stats->accum_inf, type);
3717
3718 // Show ENEMY DESTROYED ratio.
3719 //
3720 by += 7;
3721 p3 = ShowRatio(bx, by, bx + 52, by, stats->total_enemy, stats->accum_enemy, type);
3722
3723 // Show OVERALL FLOOR ratio.
3724 //
3725 by += (::is_ps() ? 13 : 12);
3726 floor = p1 + p2 + p3;
3727 ShowRatio(bx, by, bx + 52, by, maxPerFloor, floor, type);
3728
3729 // Show OVERALL MISSION ratio.
3730 //
3731 by += 7;
3732 stats->overall_floor = floor;
3733 for (loop = 0; loop < MAPS_WITH_STATS; loop++) {
3734 total += 300;
3735 mission += gamestuff.level[loop].stats.overall_floor;
3736 }
3737 mission = ShowRatio(bx, by, bx + 52, by, total, mission, type);
3738
3739 if (show_stats_quick) {
3740 VW_UpdateScreen();
3741 }
3742
3743 return mission;
3744 }
3745
ShowRatio(int16_t bx,int16_t by,int16_t nx,int16_t ny,int32_t total,int32_t perc,ss_type type)3746 uint8_t ShowRatio(
3747 int16_t bx,
3748 int16_t by,
3749 int16_t nx,
3750 int16_t ny,
3751 int32_t total,
3752 int32_t perc,
3753 ss_type type)
3754 {
3755 int8_t numbars;
3756 int8_t maxperc;
3757 int8_t percentage = 1, loop;
3758
3759 // Catch those nasty divide-by-zeros!
3760 //
3761 if (total) {
3762 maxperc = static_cast<int8_t>(LRATIO(100, total, perc, 10));
3763 numbars = LRATIO(48, 100, maxperc, 10);
3764 } else {
3765 if (type != ss_justcalc) {
3766 fontcolor = 0x57;
3767 VW_Bar(bx, by, BAR_W, BAR_H, 0);
3768 VW_Bar(nx, ny, PERC_W + 6, PERC_H, 0);
3769 PrintX = nx;
3770 PrintY = ny;
3771 US_Print("N/A");
3772 }
3773 return 100;
3774 }
3775
3776 if (type == ss_justcalc) {
3777 return maxperc;
3778 }
3779
3780 PrintY = ny;
3781 fontcolor = 0xaf;
3782 fontnumber = 2;
3783
3784 VW_Bar(bx, by, BAR_W, BAR_H, 0x07);
3785 PrintStatPercent(nx, ny, 0);
3786 for (loop = 0; loop < numbars; loop++) {
3787 if (LastScan != ScanCode::sc_none) {
3788 show_stats_quick = true;
3789 }
3790
3791 // Print one line of bar
3792 //
3793 VL_Vlin(bx++, by, BAR_H, 0xc8);
3794
3795 // Keep up with current percentage
3796 //
3797 if (loop == numbars - 1) {
3798 percentage = maxperc;
3799 } else {
3800 percentage += 2;
3801 }
3802
3803 PrintStatPercent(nx, ny, percentage);
3804
3805 if (!show_stats_quick) {
3806 if (!(loop % 2)) {
3807 ::sd_play_player_sound(STATS1SND, bstone::AC_ITEM);
3808 }
3809 VW_WaitVBL(1);
3810 VW_UpdateScreen();
3811 }
3812 }
3813
3814 if (!show_stats_quick && numbars) {
3815 ::sd_play_player_sound(STATS2SND, bstone::AC_ITEM);
3816
3817 while (::SD_SoundPlaying() && LastScan == ScanCode::sc_none) {
3818 ::in_handle_events();
3819 }
3820 }
3821
3822 return maxperc;
3823 }
3824
PrintStatPercent(int16_t nx,int16_t ny,int8_t percentage)3825 void PrintStatPercent(
3826 int16_t nx,
3827 int16_t ny,
3828 int8_t percentage)
3829 {
3830 if (percentage < 10) {
3831 PrintX = nx + 9;
3832 } else if (percentage < 100) {
3833 PrintX = nx + 4;
3834 } else {
3835 PrintX = nx - 1;
3836 }
3837
3838 VW_Bar(nx, ny, PERC_W + 5, PERC_H, 0);
3839 US_PrintUnsigned(percentage);
3840 US_Print("%");
3841 }
3842
PerfectStats()3843 bool PerfectStats()
3844 {
3845 if ((gamestuff.level[gamestate.mapon].stats.total_points ==
3846 gamestuff.level[gamestate.mapon].stats.accum_points) &&
3847 (gamestuff.level[gamestate.mapon].stats.total_inf ==
3848 gamestuff.level[gamestate.mapon].stats.accum_inf) &&
3849 (gamestuff.level[gamestate.mapon].stats.total_enemy ==
3850 gamestuff.level[gamestate.mapon].stats.accum_enemy))
3851 {
3852 return true;
3853 }
3854
3855 return false;
3856 }
3857
3858
3859 // ===========================================================================
3860 //
3861 // PINBALL BONUS DISPLAY CODE
3862 //
3863 // ===========================================================================
3864
B_GAliFunc()3865 void B_GAliFunc()
3866 {
3867 extern char B_GAlienDead2[];
3868
3869 if (gamestate.episode == 5) {
3870 DisplayInfoMsg(B_GAlienDead2, MP_PINBALL_BONUS, 7 * 60, MT_BONUS);
3871 }
3872 }
3873
B_EManFunc()3874 void B_EManFunc()
3875 {
3876 ::sd_play_player_sound(EXTRA_MANSND, bstone::AC_ITEM);
3877
3878 ::fontnumber = 2;
3879
3880 ::LatchDrawPic(0, 0, TOP_STATUSBARPIC);
3881 ::ShadowPrintLocationText(sp_normal);
3882 }
3883
B_MillFunc()3884 void B_MillFunc()
3885 {
3886 GiveAmmo(99);
3887 HealSelf(99);
3888 }
3889
B_RollFunc()3890 void B_RollFunc()
3891 {
3892 B_MillFunc();
3893 gamestate.score_roll_wait = SCORE_ROLL_WAIT;
3894 }
3895
3896
3897 char B_GAlienDead2[] = "^FC57 GUARDIAN ALIEN\r"
3898 " DESTROYED!\r\r"
3899 "^FCA6 FIND AND DESTROY ALL\r"
3900 "PROJECTION GENERATORS!";
3901
3902 char B_GAlienDead[] = "^FC57 GUARDIAN ALIEN\r"
3903 " DESTROYED!\r\r"
3904 "^FCA6 FIND THE EXIT TO\r"
3905 "COMPLETE THIS MISSION";
3906
3907 char B_ScoreRolled[] = "^FC57\rROLLED SCORE DISPLAY!\r"
3908 "^FCA6 FULL AMMO BONUS!\r"
3909 " FULL HEALTH BONUS!\r"
3910 "1,000,000 POINT BONUS!";
3911
3912 char B_OneMillion[] = "^FC57\r GREAT SCORE!\r"
3913 "^FCA6 FULL AMMO BONUS!\r"
3914 " FULL HEALTH BONUS!\r"
3915 "1,000,000 POINT BONUS!";
3916
3917 char B_ExtraMan[] = "^FC57\r\r GREAT SCORE!\r"
3918 "^FCA6 EXTRA LIFE BONUS!\r";
3919
3920 char B_EnemyDestroyed[] = "^FC57\r\r ALL ENEMY DESTROYED!\r"
3921 "^FCA6 50,000 POINT BONUS!\r";
3922
3923 char B_TotalPoints[] = "^FC57\r\r ALL POINTS COLLECTED!\r"
3924 "^FCA6 50,000 POINT BONUS!\r";
3925
3926 char B_InformantsAlive[] = "^FC57\r\r ALL INFORMANTS ALIVE!\r"
3927 "^FCA6 50,000 POINT BONUS!\r";
3928
3929
3930 PinballBonusInfo PinballBonus[] = {
3931 // Special
3932 // BonusText Points Recur? Function
3933 // -----------------------------------------------------
3934 { B_GAlienDead, 0, false, B_GAliFunc },
3935 { B_ScoreRolled, 1000000l, true, B_RollFunc },
3936 { B_OneMillion, 1000000l, false, B_MillFunc },
3937 { B_ExtraMan, 0, true, B_EManFunc },
3938 { B_EnemyDestroyed, 50000l, false, nullptr },
3939 { B_TotalPoints, 50000l, false, nullptr },
3940 { B_InformantsAlive, 50000l, false, nullptr },
3941 };
3942
DisplayPinballBonus()3943 void DisplayPinballBonus()
3944 {
3945 int8_t loop;
3946
3947 // Check queue for bonuses
3948 //
3949 for (loop = 0; loop < static_cast<int8_t>(sizeof(gamestuff.level[0].bonus_queue) * 8); loop++) {
3950 if ((BONUS_QUEUE & (1 << loop)) && (LastMsgPri < MP_PINBALL_BONUS)) {
3951 // Start this bonus!
3952 //
3953 ::sd_play_player_sound(ROLL_SCORESND, bstone::AC_ITEM);
3954
3955 DisplayInfoMsg(PinballBonus[static_cast<int>(loop)].BonusText, MP_PINBALL_BONUS, 7 * 60, MT_BONUS);
3956
3957 // Add to "shown" ... Remove from "queue"
3958 //
3959 if (!PinballBonus[static_cast<int>(loop)].Recurring) {
3960 BONUS_SHOWN |= (1 << loop);
3961 }
3962 BONUS_QUEUE &= ~(1 << loop);
3963
3964 // Give points and execute special function.
3965 //
3966 GivePoints(PinballBonus[static_cast<int>(loop)].Points, false);
3967 if (PinballBonus[static_cast<int>(loop)].func) {
3968 PinballBonus[static_cast<int>(loop)].func();
3969 }
3970 }
3971 }
3972 }
3973
CheckPinballBonus(int32_t points)3974 void CheckPinballBonus(
3975 int32_t points)
3976 {
3977 int32_t score_before = gamestate.score,
3978 score_after = gamestate.score + points;
3979
3980 // Check SCORE ROLLED bonus
3981 //
3982 if (score_before <= MAX_DISPLAY_SCORE && score_after > MAX_DISPLAY_SCORE) {
3983 ActivatePinballBonus(B_SCORE_ROLLED);
3984 }
3985
3986 // Check ONE MILLION bonus
3987 //
3988 if (score_before < 500000l && score_after >= 500000l) {
3989 ActivatePinballBonus(B_ONE_MILLION);
3990 }
3991
3992 // Check EXTRA MAN bonus
3993 //
3994 if (score_after >= gamestate.nextextra) {
3995 gamestate.nextextra += EXTRAPOINTS;
3996 if (gamestate.lives < MAX_EXTRA_LIVES) {
3997 gamestate.lives++;
3998 ActivatePinballBonus(B_EXTRA_MAN);
3999 }
4000 }
4001
4002 // Check TOTAL ENEMY bonus
4003 //
4004 if (gamestuff.level[gamestate.mapon].stats.total_enemy ==
4005 gamestuff.level[gamestate.mapon].stats.accum_enemy)
4006 {
4007 ActivatePinballBonus(B_ENEMY_DESTROYED);
4008 }
4009
4010 // Check TOTAL POINTS bonus
4011 //
4012 if (gamestuff.level[gamestate.mapon].stats.total_points ==
4013 gamestuff.level[gamestate.mapon].stats.accum_points)
4014 {
4015 ActivatePinballBonus(B_TOTAL_POINTS);
4016 }
4017
4018 // Check INFORMANTS ALIVE bonus
4019 //
4020 if ((gamestuff.level[gamestate.mapon].stats.total_inf ==
4021 gamestuff.level[gamestate.mapon].stats.accum_inf) && // All informants alive?
4022 (gamestuff.level[gamestate.mapon].stats.total_inf) && // Any informants in level?
4023 ((BONUS_SHOWN & (B_TOTAL_POINTS | B_ENEMY_DESTROYED)) ==
4024 (B_TOTAL_POINTS | B_ENEMY_DESTROYED))) // Got ENEMY and POINTS bonuses?
4025 {
4026 ActivatePinballBonus(B_INFORMANTS_ALIVE);
4027 }
4028
4029 // Display bonuses?
4030 //
4031 if (BONUS_QUEUE) {
4032 DisplayPinballBonus();
4033 }
4034 }
4035
4036
4037 /*
4038 =============================================================================
4039
4040 PLAYER CONTROL
4041
4042 =============================================================================
4043 */
4044
SpawnPlayer(int16_t tilex,int16_t tiley,int16_t dir)4045 void SpawnPlayer(
4046 int16_t tilex,
4047 int16_t tiley,
4048 int16_t dir)
4049 {
4050 if (gamestuff.level[gamestate.mapon].ptilex &&
4051 gamestuff.level[gamestate.mapon].ptiley)
4052 {
4053 tilex = gamestuff.level[gamestate.mapon].ptilex;
4054 tiley = gamestuff.level[gamestate.mapon].ptiley;
4055 dir = 1 + (gamestuff.level[gamestate.mapon].pangle / 90);
4056
4057 if (::is_aog()) {
4058 dir -= 1;
4059 }
4060 }
4061
4062 player->obclass = playerobj;
4063 player->active = ac_yes;
4064 player->tilex = static_cast<uint8_t>(tilex);
4065 player->tiley = static_cast<uint8_t>(tiley);
4066
4067 player->areanumber = GetAreaNumber(player->tilex, player->tiley);
4068
4069 player->x = ((int32_t)tilex << TILESHIFT) + TILEGLOBAL / 2;
4070 player->y = ((int32_t)tiley << TILESHIFT) + TILEGLOBAL / 2;
4071 player->state = &s_player;
4072 player->angle = (1 - dir) * 90;
4073 if (player->angle < 0) {
4074 player->angle += ANGLES;
4075 }
4076 player->flags = FL_NEVERMARK;
4077 Thrust(0, 0); // set some variables
4078
4079 InitAreas();
4080
4081 InitWeaponBounce();
4082 }
4083
GunAttack(objtype * ob)4084 void GunAttack(
4085 objtype* ob)
4086 {
4087 objtype* check, * closest, * oldclosest;
4088 int16_t damage;
4089 int16_t dx, dy, dist;
4090 int32_t viewdist;
4091 bool skip = false;
4092
4093 if (gamestate.weapon != wp_autocharge) {
4094 MakeAlertNoise(ob);
4095 }
4096
4097 switch (gamestate.weapon) {
4098 case wp_autocharge:
4099 ::sd_play_player_sound(ATKAUTOCHARGESND, bstone::AC_WEAPON);
4100
4101 skip = true;
4102 break;
4103
4104 case wp_pistol:
4105 ::sd_play_player_sound(ATKCHARGEDSND, bstone::AC_WEAPON);
4106
4107 skip = true;
4108 break;
4109
4110 case wp_burst_rifle:
4111 ::sd_play_player_sound(ATKBURSTRIFLESND, bstone::AC_WEAPON);
4112 break;
4113
4114 case wp_ion_cannon:
4115 ::sd_play_player_sound(ATKIONCANNONSND, bstone::AC_WEAPON);
4116 break;
4117
4118 }
4119
4120 //
4121 // find potential targets
4122 //
4123
4124 viewdist = 0x7fffffffl;
4125 closest = nullptr;
4126
4127 while (true) {
4128 oldclosest = closest;
4129
4130 for (check = ob->next; check; check = check->next)
4131 {
4132 if ((check->flags & FL_SHOOTABLE) &&
4133 (check->flags & FL_VISABLE) &&
4134 (std::abs(check->viewx - ::centerx) < shootdelta))
4135 {
4136 if (check->transx < viewdist) {
4137 if ((skip && (check->obclass == hang_terrotobj))
4138 || (check->flags2 & FL2_NOTGUNSHOOTABLE))
4139 {
4140 continue;
4141 }
4142
4143 viewdist = check->transx;
4144 closest = check;
4145 }
4146 }
4147 }
4148
4149 if (closest == oldclosest) {
4150 return; // no more targets, all missed
4151
4152 }
4153 //
4154 // trace a line from player to enemey
4155 //
4156 if (CheckLine(closest, player)) {
4157 break;
4158 }
4159 }
4160
4161 //
4162 // hit something
4163 //
4164
4165 dx = static_cast<int16_t>(abs(closest->tilex - player->tilex));
4166 dy = static_cast<int16_t>(abs(closest->tiley - player->tiley));
4167 dist = dx > dy ? dx : dy;
4168
4169 if (dist < 2) {
4170 damage = US_RndT() / 2; // 4
4171 } else if (dist < 4) {
4172 damage = US_RndT() / 4; // 6
4173 } else {
4174 if ((US_RndT() / 12) < dist) { // missed
4175 return;
4176 }
4177 damage = US_RndT() / 4; // 6
4178 }
4179
4180 DamageActor(closest, damage, player);
4181 }
4182
T_Attack(objtype * ob)4183 void T_Attack(
4184 objtype* ob)
4185 {
4186 atkinf_t* cur;
4187 int16_t x;
4188
4189 if (noShots) {
4190 ob->state = &s_player;
4191 gamestate.attackframe = gamestate.weaponframe = 0;
4192 return;
4193 }
4194
4195 if (gamestate.weapon == wp_autocharge) {
4196 UpdateAmmoMsg();
4197 }
4198
4199 if (buttonstate[bt_use] && !buttonheld[bt_use]) {
4200 buttonstate[bt_use] = false;
4201 }
4202
4203 if (buttonstate[bt_attack] && !buttonheld[bt_attack]) {
4204 buttonstate[bt_attack] = false;
4205 }
4206
4207 ControlMovement(ob);
4208
4209 player->tilex = static_cast<uint8_t>(player->x >> TILESHIFT); // scale to tile values
4210 player->tiley = static_cast<uint8_t>(player->y >> TILESHIFT);
4211
4212 //
4213 // change frame and fire
4214 //
4215 gamestate.attackcount -= tics;
4216 if (gamestate.attackcount <= 0) {
4217 cur = &attackinfo[static_cast<int>(gamestate.weapon)][gamestate.attackframe];
4218 switch (cur->attack) {
4219 case -1:
4220 ob->state = &s_player;
4221
4222 if (!gamestate.ammo) {
4223 if (gamestate.weapon != wp_autocharge) {
4224 gamestate.weapon = wp_autocharge;
4225 DrawWeapon();
4226 DisplayInfoMsg(EnergyPackDepleted, MP_NO_MORE_AMMO, DISPLAY_MSG_STD_TIME << 1, MT_OUT_OF_AMMO);
4227 }
4228 } else {
4229 if (!(gamestate.useable_weapons & (1 << gamestate.weapon))) {
4230 gamestate.weapon = wp_autocharge;
4231 DrawWeapon();
4232 DisplayInfoMsg(NotEnoughEnergyForWeapon, MP_NO_MORE_AMMO, DISPLAY_MSG_STD_TIME << 1, MT_OUT_OF_AMMO);
4233 }
4234 }
4235 gamestate.attackframe = gamestate.weaponframe = 0;
4236 return;
4237
4238 case -2:
4239 ob->state = &s_player;
4240 if (!gamestate.plasma_detonators) {
4241 // Check to see what weapons are possible.
4242 //
4243 const auto n_x = static_cast<int16_t>(::is_ps() ? wp_bfg_cannon : wp_grenade);
4244
4245 for (x = n_x; x >= wp_autocharge; x--) {
4246 if (gamestate.useable_weapons & (1 << x)) {
4247 gamestate.weapon = static_cast<int8_t>(x);
4248 break;
4249 }
4250 }
4251
4252 DrawWeapon();
4253 }
4254 gamestate.attackframe = gamestate.weaponframe = 0;
4255 return;
4256
4257 case 4:
4258 if (!gamestate.ammo) {
4259 break;
4260 }
4261 if (buttonstate[bt_attack]) {
4262 gamestate.attackframe -= 2;
4263 }
4264
4265 case 0:
4266 if (gamestate.weapon == wp_grenade) {
4267 if (!objfreelist) {
4268 DISPLAY_TIMED_MSG(WeaponMalfunction, MP_WEAPON_MALFUNCTION, MT_MALFUNCTION);
4269 gamestate.attackframe++;
4270 }
4271 }
4272 break;
4273
4274 case 1:
4275 if (!gamestate.ammo) { // can only happen with chain gun
4276 gamestate.attackframe++;
4277 break;
4278 }
4279 GunAttack(ob);
4280 if (!godmode) {
4281 gamestate.ammo--;
4282 }
4283 if (!::is_ps()) {
4284 DrawWeapon();
4285 } else {
4286 DrawAmmo(false);
4287 }
4288 break;
4289
4290 case 2:
4291 if (gamestate.weapon_wait) {
4292 break;
4293 }
4294 GunAttack(ob);
4295 gamestate.weapon_wait = AUTOCHARGE_WAIT;
4296 if (!::is_ps()) {
4297 DrawWeapon();
4298 } else {
4299 DrawAmmo(false);
4300 }
4301 break;
4302
4303 case 3:
4304 if (gamestate.ammo && buttonstate[bt_attack]) {
4305 gamestate.attackframe -= 2;
4306 }
4307 break;
4308
4309 case 6:
4310 if (gamestate.ammo && buttonstate[bt_attack]) {
4311 if (objfreelist) {
4312 gamestate.attackframe -= 2;
4313 }
4314 } else if (gamestate.ammo == 0) {
4315 DISPLAY_TIMED_MSG(WeaponMalfunction, MP_WEAPON_MALFUNCTION, MT_MALFUNCTION);
4316 }
4317 break;
4318
4319 case 5:
4320 if (!objfreelist) {
4321 DISPLAY_TIMED_MSG(WeaponMalfunction, MP_WEAPON_MALFUNCTION, MT_MALFUNCTION);
4322 gamestate.attackframe++;
4323 } else {
4324 if (LastMsgType == MT_MALFUNCTION) {
4325 MsgTicsRemain = 1; // Clear Malfuction Msg before anim
4326
4327 }
4328 if (!godmode) {
4329 if (gamestate.ammo >= GRENADE_ENERGY_USE) {
4330 gamestate.ammo -= GRENADE_ENERGY_USE;
4331 if (!::is_ps()) {
4332 DrawWeapon();
4333 } else {
4334 DrawAmmo(false);
4335 }
4336 } else {
4337 gamestate.attackframe++;
4338 }
4339 }
4340
4341 ::sd_play_player_sound(ATKGRENADESND, bstone::AC_WEAPON);
4342
4343 SpawnProjectile(ob, grenadeobj);
4344 MakeAlertNoise(ob);
4345 }
4346 break;
4347
4348 case 7:
4349 TryDropPlasmaDetonator();
4350 DrawAmmo(false);
4351 break;
4352
4353 case 8:
4354 if (gamestate.plasma_detonators && buttonstate[bt_attack]) {
4355 gamestate.attackframe -= 2;
4356 }
4357 break;
4358
4359 case 9:
4360 if (!objfreelist) {
4361 DISPLAY_TIMED_MSG(WeaponMalfunction, MP_WEAPON_MALFUNCTION, MT_MALFUNCTION);
4362 gamestate.attackframe++;
4363 } else {
4364 if (LastMsgType == MT_MALFUNCTION) {
4365 MsgTicsRemain = 1; // Clear Malfuction Msg before anim
4366
4367 }
4368 if (!godmode) {
4369 if (gamestate.ammo >= BFG_ENERGY_USE) {
4370 gamestate.ammo -= BFG_ENERGY_USE;
4371 DrawAmmo(false);
4372 } else {
4373 gamestate.attackframe++;
4374 }
4375 }
4376
4377 ::sd_play_player_sound(ATKIONCANNONSND, bstone::AC_WEAPON);
4378
4379 SpawnProjectile(ob, bfg_shotobj);
4380 MakeAlertNoise(ob);
4381 }
4382 break;
4383
4384 case 10:
4385 if (gamestate.ammo && buttonstate[bt_attack]) {
4386 if (objfreelist) {
4387 gamestate.attackframe -= 2;
4388 }
4389 } else if (gamestate.ammo == 0) {
4390 DISPLAY_TIMED_MSG(WeaponMalfunction, MP_WEAPON_MALFUNCTION, MT_MALFUNCTION);
4391 }
4392 break;
4393 }
4394
4395 gamestate.attackcount += cur->tics;
4396 gamestate.attackframe++;
4397 gamestate.weaponframe =
4398 attackinfo[static_cast<int>(gamestate.weapon)][gamestate.attackframe].frame;
4399 }
4400 }
4401
T_Player(objtype * ob)4402 void T_Player(
4403 objtype* ob)
4404 {
4405 CheckWeaponChange();
4406
4407 if (gamestate.weapon == wp_autocharge) {
4408 UpdateAmmoMsg();
4409 }
4410
4411 if (::is_ps()) {
4412 if (tryDetonatorDelay > tics) {
4413 tryDetonatorDelay -= tics;
4414 } else {
4415 tryDetonatorDelay = 0;
4416 }
4417 }
4418
4419 if (buttonstate[bt_use]) {
4420 bool play_hit_wall_sound;
4421
4422 Cmd_Use(play_hit_wall_sound);
4423
4424 if (play_hit_wall_sound) {
4425 ::sd_play_player_sound(HITWALLSND, bstone::AC_HIT_WALL);
4426 }
4427 }
4428
4429 if (buttonstate[bt_attack] && !buttonheld[bt_attack]) {
4430 Cmd_Fire();
4431 }
4432
4433 ControlMovement(ob);
4434 HandleWeaponBounce();
4435
4436 player->tilex = static_cast<uint8_t>(player->x >> TILESHIFT); // scale to tile values
4437 player->tiley = static_cast<uint8_t>(player->y >> TILESHIFT);
4438 }
4439
RunBlakeRun()4440 void RunBlakeRun()
4441 {
4442 const fixed BLAKE_SPEED = MOVESCALE * 50;
4443
4444 int32_t xmove, ymove;
4445 objtype* blake;
4446 int16_t startx, starty, dx, dy;
4447
4448 // Spawn Blake and set pointer.
4449 //
4450 SpawnPatrol(en_blake, player->tilex, player->tiley, static_cast<int16_t>(player->dir >> 1));
4451 blake = new_actor;
4452
4453 // Blake object starts one tile behind player object.
4454 //
4455 switch (blake->dir) {
4456 case north:
4457 blake->tiley += 2;
4458 break;
4459
4460 case south:
4461 blake->tiley -= 2;
4462 break;
4463
4464 case east:
4465 blake->tilex -= 2;
4466 break;
4467
4468 case west:
4469 blake->tilex += 2;
4470 break;
4471
4472 default:
4473 break;
4474 }
4475
4476 // Align Blake on the middle of the tile.
4477 //
4478 blake->x = ((int32_t)blake->tilex << TILESHIFT) + TILEGLOBAL / 2;
4479 blake->y = ((int32_t)blake->tiley << TILESHIFT) + TILEGLOBAL / 2;
4480 blake->tilex = static_cast<uint8_t>(blake->x >> TILESHIFT);
4481 startx = blake->tilex;
4482 blake->tiley = static_cast<uint8_t>(blake->y >> TILESHIFT);
4483 starty = blake->tiley;
4484
4485 // Run, Blake, Run!
4486 //
4487 do {
4488 // Calc movement in X and Y directions.
4489 //
4490 xmove = FixedByFrac(BLAKE_SPEED, costable[player->angle]);
4491 ymove = -FixedByFrac(BLAKE_SPEED, sintable[player->angle]);
4492
4493 // Move, animate, and redraw.
4494 //
4495 if (ClipMove(blake, xmove, ymove)) {
4496 break;
4497 }
4498 DoActor(blake);
4499 ThreeDRefresh();
4500
4501 // Calc new tile X/Y.
4502 //
4503 blake->tilex = static_cast<uint8_t>(blake->x >> TILESHIFT);
4504 blake->tiley = static_cast<uint8_t>(blake->y >> TILESHIFT);
4505
4506 // Evaluate distance from start.
4507 //
4508 dx = blake->tilex - startx;
4509 dx = ABS(dx);
4510 dy = blake->tiley - starty;
4511 dy = ABS(dy);
4512
4513 // BBi
4514 ::in_handle_events();
4515 } while ((dx < 6) && (dy < 6));
4516 }
4517
SW_HandleActor(objtype * obj)4518 void SW_HandleActor(
4519 objtype* obj)
4520 {
4521 if (!obj->active) {
4522 obj->active = ac_yes;
4523 }
4524
4525 switch (obj->obclass) {
4526 case rentacopobj:
4527 case gen_scientistobj:
4528 case swatobj:
4529 case goldsternobj:
4530 case proguardobj:
4531 if (!(obj->flags & (FL_ATTACKMODE | FL_FIRSTATTACK))) {
4532 FirstSighting(obj);
4533 }
4534 break;
4535
4536 case morphing_spider_mutantobj:
4537 case morphing_reptilian_warriorobj:
4538 case morphing_mutanthuman2obj:
4539 case crate1obj:
4540 case crate2obj:
4541 case crate3obj:
4542 case podeggobj:
4543 KillActor(obj);
4544 break;
4545
4546 case gurney_waitobj:
4547 case scan_wait_alienobj:
4548 case lcan_wait_alienobj:
4549 break;
4550
4551 case floatingbombobj:
4552 case volatiletransportobj:
4553 if (obj->flags & FL_STATIONARY) {
4554 KillActor(obj);
4555 } else if (!(obj->flags & (FL_ATTACKMODE | FL_FIRSTATTACK))) {
4556 FirstSighting(obj);
4557 }
4558 break;
4559
4560 case spider_mutantobj:
4561 case breather_beastobj:
4562 case cyborg_warriorobj:
4563 case reptilian_warriorobj:
4564 case acid_dragonobj:
4565 case mech_guardianobj:
4566 case liquidobj:
4567 case genetic_guardobj:
4568 case mutant_human1obj:
4569 case mutant_human2obj:
4570 case lcan_alienobj:
4571 case scan_alienobj:
4572 case gurneyobj:
4573 case podobj:
4574 case final_boss1obj:
4575 case final_boss2obj:
4576 case final_boss3obj:
4577 case final_boss4obj:
4578 if (!(obj->flags & (FL_ATTACKMODE | FL_FIRSTATTACK))) {
4579 FirstSighting(obj);
4580 }
4581 break;
4582
4583 case post_barrierobj:
4584 case arc_barrierobj:
4585 break;
4586
4587 default:
4588 break;
4589 }
4590 }
4591
4592 // -------------------------------------------------------------------------
4593 // SW_HandleStatic() - Handle all statics connected to a smart switch.
4594 // -------------------------------------------------------------------------
SW_HandleStatic(statobj_t * stat,uint16_t tilex,uint16_t tiley)4595 void SW_HandleStatic(
4596 statobj_t* stat,
4597 uint16_t tilex,
4598 uint16_t tiley)
4599 {
4600 switch (stat->itemnumber) {
4601 case bo_clip:
4602 case bo_clip2:
4603 if (::is_ps()) {
4604 SpawnCusExplosion((((fixed)tilex) << TILESHIFT) + 0x7FFF,
4605 (((fixed)tiley) << TILESHIFT) + 0x7FFF,
4606 SPR_CLIP_EXP1, 7, 30 + (US_RndT() & 0x27), explosionobj);
4607 }
4608 stat->shapenum = -1;
4609 stat->itemnumber = bo_nothing;
4610 break;
4611 }
4612 }
4613
4614 // -------------------------------------------------------------------------
4615 // OperateSmartSwitch() - Operates a Smart Switch
4616 //
4617 // PARAMETERS:
4618 // tilex - Tile X coord that the Smart switch points to.
4619 // tiley - Tile Y coord that the Smart switch points to.
4620 // force - Force switch operation. Will not check the players current
4621 // and last tilex & tiley coords. This is usefull for other
4622 // actors toggling barrier switches.
4623 //
4624 // RETURNS: Boolean: TRUE - Remove switch from map
4625 // FALSE - Keep switch in map
4626 //
4627 // -------------------------------------------------------------------------
OperateSmartSwitch(uint16_t tilex,uint16_t tiley,int8_t Operation,bool Force)4628 bool OperateSmartSwitch(
4629 uint16_t tilex,
4630 uint16_t tiley,
4631 int8_t Operation,
4632 bool Force)
4633 {
4634 enum what_is_it {
4635 wit_NOTHING,
4636 wit_DOOR,
4637 wit_WALL,
4638 wit_STATIC,
4639 wit_ACTOR
4640 }; // what_is_it
4641
4642 what_is_it WhatItIs;
4643 objtype* obj;
4644 statobj_t* stat = nullptr;
4645 uint8_t tile, DoorNum = 0;
4646 uint16_t iconnum;
4647
4648 //
4649 // Get some information about what
4650 // this switch is pointing to.
4651 //
4652
4653 tile = tilemap[tilex][tiley];
4654 obj = actorat[tilex][tiley];
4655 iconnum = *(mapsegs[1] + farmapylookup[tiley] + tilex);
4656 WhatItIs = wit_NOTHING;
4657
4658 //
4659 // Deterimine if the switch points to an
4660 // actor, door, wall, static or is Special.
4661 //
4662
4663 if (obj < objlist) {
4664 if (obj == (objtype*)1 && tile == 0) {
4665 // We have a SOLID static!
4666
4667 WhatItIs = wit_STATIC;
4668 } else {
4669 if (tile) {
4670 //
4671 // We have a wall of some type (maybe a door).
4672 //
4673
4674 if (tile & 0x80) {
4675 // We have a door
4676
4677 WhatItIs = wit_DOOR;
4678 DoorNum = tile & 0x7f;
4679 } else {
4680 // We have a wall
4681
4682 WhatItIs = wit_WALL;
4683 }
4684 } else {
4685 stat = FindStatic(tilex, tiley);
4686
4687 if (stat) {
4688 WhatItIs = wit_STATIC;
4689 }
4690 }
4691 }
4692 } else {
4693 if (obj < &objlist[MAXACTORS]) {
4694 // We have an actor.
4695
4696 WhatItIs = wit_ACTOR;
4697 } else {
4698 WhatItIs = wit_NOTHING;
4699 }
4700 }
4701
4702 //
4703 // Ok... Now do that voodoo that you do so well...
4704 //
4705
4706 switch (WhatItIs) {
4707 //
4708 // Handle Doors
4709 //
4710 case wit_DOOR:
4711 if (doorobjlist[DoorNum].action == dr_jammed) {
4712 return false;
4713 }
4714
4715 doorobjlist[DoorNum].lock = kt_none;
4716 OpenDoor(DoorNum);
4717 return false;
4718
4719
4720 //
4721 // Handle Actors
4722 //
4723 case wit_ACTOR:
4724 if (!(obj->flags & FL_DEADGUY)) {
4725 SW_HandleActor(obj);
4726 }
4727 return true;
4728
4729
4730 //
4731 // Handle Walls
4732 //
4733 case wit_WALL: {
4734 if (Force || player_oldtilex != player->tilex || player_oldtiley != player->tiley) {
4735 switch (tile) {
4736 case OFF_SWITCH:
4737 if (Operation == ST_TURN_OFF) {
4738 return false;
4739 }
4740
4741 ActivateWallSwitch(iconnum, tilex, tiley);
4742 break;
4743
4744 case ON_SWITCH:
4745 if (Operation == ST_TURN_ON) {
4746 return false;
4747 }
4748 ActivateWallSwitch(iconnum, tilex, tiley);
4749 break;
4750 }
4751 }
4752 }
4753 return false;
4754
4755
4756 //
4757 // Handle Statics
4758 //
4759 case wit_STATIC:
4760 stat = ::FindStatic(tilex, tiley);
4761
4762 if (!stat) {
4763 return false;
4764 }
4765
4766 SW_HandleStatic(stat, tilex, tiley);
4767 return true;
4768
4769
4770 //
4771 // Handle NON connected smart switches...
4772 //
4773 case wit_NOTHING:
4774 // Actor (or something) that was to be triggered has
4775 // moved... SSSOOOoo, Remove the switch.
4776 return true;
4777 }
4778
4779 return false;
4780 }
4781
4782
4783 // ==========================================================================
4784 //
4785 // WEAPON BOUNCE CODE
4786 //
4787 // ==========================================================================
4788
4789 #define wb_MaxPoint ((int32_t)10 << TILESHIFT)
4790 #define wb_MidPoint ((int32_t)6 << TILESHIFT)
4791 #define wb_MinPoint ((int32_t)2 << TILESHIFT)
4792 #define wb_MaxGoalDist (wb_MaxPoint - wb_MidPoint)
4793
4794 #define wb_MaxOffset (wb_MaxPoint + ((int32_t)2 << TILESHIFT))
4795 #define wb_MinOffset (wb_MinPoint - ((int32_t)2 << TILESHIFT))
4796
4797 extern fixed bounceOffset;
4798
4799 fixed bounceVel, bounceDest;
4800 int16_t bounceOk;
4801
InitWeaponBounce()4802 void InitWeaponBounce()
4803 {
4804 bounceOffset = wb_MidPoint;
4805 bounceDest = wb_MaxPoint;
4806 bounceVel = bounceOk = 0;
4807 }
4808
HandleWeaponBounce()4809 void HandleWeaponBounce()
4810 {
4811 int bounceSpeed;
4812
4813 bounceSpeed = 90 - ((20 - viewsize) * 6);
4814
4815 if (bounceOk) {
4816 if (bounceOffset < bounceDest) {
4817 bounceVel += (sintable[bounceSpeed] + 1) >> 1;
4818 bounceOffset += bounceVel;
4819 if (bounceOffset > bounceDest) {
4820 bounceDest = wb_MinPoint;
4821 bounceVel >>= 2;
4822 }
4823 } else if (bounceOffset > bounceDest) {
4824 bounceVel -= sintable[bounceSpeed] >> 2;
4825 bounceOffset += bounceVel;
4826
4827 if (bounceOffset < bounceDest) {
4828 bounceDest = wb_MaxPoint;
4829 bounceVel >>= 2;
4830 }
4831 }
4832 } else {
4833 if (bounceOffset > wb_MidPoint) {
4834 bounceOffset -= ((int32_t)2 << TILESHIFT);
4835 if (bounceOffset < wb_MidPoint) {
4836 bounceOffset = wb_MidPoint;
4837 }
4838 } else if (bounceOffset < wb_MidPoint) {
4839 bounceOffset += ((int32_t)2 << TILESHIFT);
4840 if (bounceOffset > wb_MidPoint) {
4841 bounceOffset = wb_MidPoint;
4842 }
4843 }
4844
4845 bounceDest = wb_MaxPoint;
4846 bounceVel = 0;
4847 }
4848
4849 if (bounceOffset > wb_MaxOffset) {
4850 bounceOffset = wb_MaxOffset;
4851 } else if (bounceOffset < wb_MinOffset) {
4852 bounceOffset = wb_MinOffset;
4853 }
4854 }
4855