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