1 /*
2 
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
4 
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 */
19 /*
20  * $Source: r:/prj/cit/src/RCS/mfdgames.c $
21  * $Revision: 1.45 $
22  * $Author: buzzard $
23  * $Date: 1994/11/22 09:32:36 $
24  *
25  */
26 
27 #include <string.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 
31 #include "faketime.h"
32 #include "player.h"
33 #include "newmfd.h"
34 #include "mfdint.h"
35 #include "mfddims.h"
36 #include "colors.h"
37 #include "rcolors.h"
38 #include "tools.h"
39 #include "mainloop.h"
40 #include "gameloop.h"
41 #include "wares.h"
42 #include "amap.h"
43 #include "objprop.h"
44 #include "objsim.h"
45 #include "miscqvar.h"
46 #include "cit2d.h"
47 #include "diffq.h"
48 #include "mfdgames.h"
49 #include "softdef.h"
50 
51 #include "sfxlist.h"
52 #include "musicai.h"
53 #include "gamestrn.h"
54 
55 #include "cybstrng.h"
56 #include "otrip.h"
57 #include "mfdart.h"
58 #include "gamescr.h"
59 #include "gr2ss.h"
60 
61 #ifdef LOST_TREASURES_OF_MFD_GAMES
62 #include "minimax.h"
63 #include "limits.h"
64 #endif
65 
66 // -------
67 // Globals
68 // -------
69 
70 #ifdef LOST_TREASURES_OF_MFD_GAMES
71 extern void fstack_init(uchar *fs, uint siz);
72 #endif
73 
74 #define PUZZLE_DIFFICULTY (QUESTVAR_GET(PUZZLE_DIFF_QVAR))
75 
76 // special storage used by mfdgames and by wiring puzzles
77 // but note it's not managed correctly across save/restore etc.
78 //   Hey, this used to be 1024!
79 #define HIDEOUS_GAME_STORAGE 2048
80 unsigned char hideous_secret_game_storage[HIDEOUS_GAME_STORAGE + 4];
81 
82 // stuff to check magic cookie in secret game stuff
83 #define COOKIE (*((ulong *)hideous_secret_game_storage))
84 
85 #define COOK_VAL(a, b, c, d) ((((a)*256 + (b)) * 256 + (c)) * 256 + (d))
86 #define GAME_COOK(d) COOK_VAL('G', 'a', 'm', (d))
87 
88 #define NUM_GAMES 8
89 
90 LGRect GamesMenu;
91 static long score_time = 0;
92 extern uchar full_game_3d;
93 
94 static int games_time_diff;
95 
96 void Rect_gr_box(LGRect *r);
97 void Rect_gr_rect(LGRect *r);
98 
99 #define STRING(x) get_temp_string(REF_STR_##x)
100 
101 // note everything crammed into chars due to all mfds always active all have memory always architechture
102 // if they were ints, all would work, and be much happier to boot
103 
104 // note that the starting fields of this must match the
105 // starting fields of Robot Invasion, since they share
106 // ball and player movement code
107 typedef struct {
108     uchar game_mode;
109     char ball_pos_x, ball_pos_y;
110     char ball_dir_x, ball_dir_y;
111     char p_pos, p_spd;
112     char c_pos, c_spd;
113     uchar c_score;
114     uchar p_score;
115     uchar last_point;
116     uchar game_won;
117 } pong_state;
118 
119 typedef struct {
120     uchar game_mode;
121     ushort save_time;
122     // current QIX endpoints
123     char x[2], dx[2], y[2], dy[2];
124     uchar color;
125 } menu_state;
126 
127 typedef struct {
128     uchar game_mode;  // current game mode
129     uchar lane_cnt;   // current lane count
130     uchar diff;       // 0-0xf chance of a new car per lane
131     char player_lane; // current player line
132     char player_move; // which way we are going
133     ushort lanes[8];  // two bits for each
134     uchar game_state; // have we lost
135     uint frame_cnt;   // how many frames at this difficulty
136     //   uchar  last_bs;       // last button state
137 } road_state;
138 
139 typedef struct {
140     // shared state with Ping
141     uchar game_mode;
142     char ball_pos_x, ball_pos_y;
143     char ball_dir_x, ball_dir_y;
144     char p_pos, p_spd;
145 
146     // brick state
147     ushort rows[3]; // BOTS_NUM_ROWS
148     short hpos;
149     char hspd;
150     char vpos;
151     char balls;
152 } bots_state;
153 
154 #define PONG_X_DIR_MAX 7
155 
156 #define GAME_DATA_SIZE 32
157 #define GAME_DATA (&player_struct.mfd_access_puzzles[0])
158 #define GAME_DATA_2 (&player_struct.mfd_func_data[MFD_GAMES_FUNC][0])
159 #define GAME_MODE (*((uchar *)GAME_DATA))
160 
161 #define GAME_MODE_MENU NUM_GAMES
162 
163 //------------
164 // PROTOTYPES
165 //------------
166 void games_expose_pong(MFD *m, ubyte control);
167 void games_expose_null(MFD *m, ubyte control);
168 void games_expose_menu(MFD *m, ubyte control);
169 static void games_expose_mcom(MFD *m, ubyte control);
170 void games_expose_bots(MFD *m, ubyte control);
171 void games_expose_road(MFD *m, ubyte control);
172 
173 void games_init_pong(void *game_state);
174 static void games_init_mcom(void *game_state);
175 void games_init_road(void *game_state);
176 void games_init_null(void *game_state);
177 void games_init_bots(void *game_state);
178 
179 #ifdef LOST_TREASURES_OF_MFD_GAMES
180 void games_expose_15(MFD *m, ubyte control);
181 void games_init_15(void *game_state);
182 uchar games_handle_15(MFD *m, uiEvent *e);
183 
184 void games_expose_ttt(MFD *m, ubyte control);
185 void games_init_ttt(void *game_state);
186 uchar games_handle_ttt(MFD *m, uiEvent *e);
187 
188 void games_expose_wing(MFD *m, ubyte control);
189 void games_init_wing(void *game_state);
190 uchar games_handle_wing(MFD *m, uiEvent *e);
191 #else
192 #define games_expose_15 games_expose_null
193 #define games_init_15 games_init_null
194 #define games_handle_15 games_handle_null
195 
196 #define games_expose_ttt games_expose_null
197 #define games_init_ttt games_init_null
198 #define games_handle_ttt games_handle_null
199 
200 #define games_expose_wing games_expose_null
201 #define games_init_wing games_init_null
202 #define games_handle_wing games_handle_null
203 #endif
204 
205 uchar games_handle_pong(MFD *m, uiEvent *e);
206 uchar games_handle_road(MFD *m, uiEvent *e);
207 uchar games_handle_menu(MFD *m, uiEvent *ev);
208 uchar games_handle_null(MFD *m, uiEvent *ev);
209 
210 void games_run_pong(pong_state *work_ps);
211 void games_run_road(road_state *work_ps);
212 void games_run_bots(bots_state *bs);
213 
214 static void mcom_start_level(void);
215 int tictactoe_evaluator(void *pos);
216 
217 void mfd_games_turnon(bool, uchar);
218 void mfd_games_turnoff(bool, uchar);
219 
220 void (*game_expose_funcs[])(MFD *m, ubyte control) = {games_expose_pong, games_expose_mcom, games_expose_road,
221                                                       games_expose_bots, games_expose_15,   games_expose_ttt,
222                                                       games_expose_null, games_expose_wing, games_expose_menu};
223 
224 void (*game_init_funcs[])(void *game_data) = {games_init_pong, games_init_mcom, games_init_road,
225                                               games_init_bots, games_init_15,   games_init_ttt,
226                                               games_init_null, games_init_wing, games_init_null};
227 
228 extern uchar (*game_handler_funcs[])(MFD *m, uiEvent *ev);
229 
230 #define NORMAL_DISPLAY 0
231 #define SCORE_DISPLAY 1
232 #define WIN_DISPLAY 2
233 
234 #define WIN_PAUSE (4 * CIT_CYCLE)
235 #define SCORE_PAUSE (2 * CIT_CYCLE)
236 
237 #define MFD_VIEW_MID (MFD_VIEW_WID / 2)
238 
239 // ===========================================================================
240 //                         * THE MFD GAMES CODE *
241 // ===========================================================================
mfd_games_init(MFD_Func * m)242 errtype mfd_games_init(MFD_Func *m) {
243     GamesMenu.ul.x = 1;
244     GamesMenu.ul.y = 1;
245     GamesMenu.lr.x = 6;
246     GamesMenu.lr.y = 6;
247     player_struct.mfd_func_status[MFD_GAMES_FUNC] |= 1u << 4u;
248 
249     return OK;
250 }
251 
252 // ---------------------------------------------------------------------------
253 // mfd_games_handler()
mfd_games_handler(MFD * m,uiEvent * e)254 uchar mfd_games_handler(MFD *m, uiEvent *e) {
255     int cur_mode = GAME_MODE;
256     uchar retval = (*game_handler_funcs[cur_mode])(m, e);
257     LGRect r;
258 
259     // detect if you have games
260     // if (player_struct.hardwarez[HARDWARE_AUTOMAP] == 0) return FALSE;
261 
262     if (!(e->mouse_data.action & MOUSE_LDOWN))
263         return FALSE; // ignore click releases
264 
265     // Did the user click in the "menu" clickbox?
266     RECT_OFFSETTED_RECT(&GamesMenu, m->rect.ul, &r);
267     if (RECT_TEST_PT(&r, e->pos)) {
268         GAME_MODE = GAME_MODE_MENU;
269         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
270         retval = TRUE;
271     }
272     return retval;
273 }
274 
275 // ---------------------------------------------------------------------------
276 // mfd_games_expose()
277 //
278 
mfd_games_expose(MFD * m,ubyte control)279 void mfd_games_expose(MFD *m, ubyte control) {
280     int cur_mode;
281     grs_font *fon;
282     ulong dt = player_struct.deltat;
283     extern int mfd_slot_primary(int slot);
284 
285     if (control & MFD_EXPOSE) {
286 
287         gr_push_canvas(pmfd_canvas);
288         ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
289         mfd_clear_rects();
290 
291         fon = ResLock(RES_tinyTechFont);
292         gr_set_font(fon);
293 
294         cur_mode = GAME_MODE;
295 #ifdef PLAYTEST
296         // so, like, this code is totally meaningless.
297         // I'm glad WATCOM bothers to warn about it.
298         // if anyone fixes it, probably should make
299         // cur_mode = GAME_MODE_MENU not 0 (ping)
300         if ((cur_mode < 0) && (cur_mode > NUM_GAMES))
301             cur_mode = 0;
302 #endif
303         if (COOKIE != GAME_COOK(GAME_MODE)) {
304             // hey, our secret storage data became invalid...
305             // umm, so, umm, what to do?  Hey, let's just pop
306             // them back into menu mode for now
307             cur_mode = GAME_MODE = GAME_MODE_MENU;
308             COOKIE = GAME_COOK(cur_mode);
309             // note we rely on the fact that if someone else
310             // stomps GAME_MODE to GAME_MODE_MENU, this will
311             // get caught by our cookie test, which will then
312             // result in us being able to initialize the save_time field
313             ((menu_state *)GAME_DATA)->save_time = 0;
314         }
315         if (m->id == mfd_slot_primary(MFD_INFO_SLOT))
316             games_time_diff += dt;
317         (*game_expose_funcs[cur_mode])(m, control);
318 
319         if (GAME_MODE != GAME_MODE_MENU) {
320             gr_set_fcolor(MFD_BTTN_FLASH);
321             Rect_gr_rect(&GamesMenu);
322             gr_set_fcolor(WHITE);
323             Rect_gr_box(&GamesMenu);
324         }
325 
326         ResUnlock(RES_tinyTechFont);
327 
328         gr_pop_canvas();
329         mfd_update_rects(m);
330     }
331 
332 }
333 
334 #define MENU_GAMELIST_X 5
335 #define MENU_GAMELIST_Y 15
336 #define MENU_GAMELIST_DY 7
337 
338 #define TIME_TIL_SCREEN_SAVE (60 * 30) // 30 fps, 60 frames
339 
340 #define MAX_LINES 12
341 int ss_head = 0;
342 typedef struct {
343     char x1, y1, x2, y2, c;
344 } oldLines;
345 oldLines *old_lines = (oldLines *)(hideous_secret_game_storage + 4);
346 
init_screen_save(menu_state * ms)347 static void init_screen_save(menu_state *ms) {
348     int i;
349     for (i = 0; i < MAX_LINES; ++i)
350         old_lines[i].x1 = old_lines[i].y1 = old_lines[i].x2 = old_lines[i].y2 = old_lines[i].c = 0;
351 
352     ms->x[0] = MFD_VIEW_WID / 2 - 4;
353     ms->x[1] = MFD_VIEW_WID / 2 + 4;
354     ms->y[0] = ms->y[1] = MFD_VIEW_HGT / 2;
355     ms->dx[0] = 1;
356     ms->dy[0] = 1;
357     ms->dx[1] = 2;
358     ms->dy[1] = -1;
359     ms->color = 128;
360 }
361 
362 #define draw_shadowed_text(s, x, y) draw_shadowed_string((s), (x), (y), full_game_3d)
363 
games_expose_menu(MFD * m,ubyte control)364 void games_expose_menu(MFD *m, ubyte control) {
365     char buf[80];
366     uint32_t i;
367     ubyte cur_games = player_struct.softs.misc[SOFTWARE_GAMES];
368     menu_state *ms = ((menu_state *)GAME_DATA);
369     if (!full_game_3d)
370         draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
371     if (ms->save_time < TIME_TIL_SCREEN_SAVE) {
372         ++ms->save_time;
373         strcpy(buf, STRING(GamesMenu));
374         gr_string_wrap(buf, MFD_VIEW_WID - 8);
375         gr_set_fcolor(RED_8_BASE + 4);
376         draw_shadowed_text(buf, 4, 1);
377 
378         strcpy(buf, STRING(DontPlay));
379         gr_string_wrap(buf, MFD_VIEW_WID - 5);
380         draw_shadowed_text(buf, 10, MFD_VIEW_HGT - 14);
381 
382         gr_set_fcolor(GREEN_8_BASE + 3);
383         for (i = 0; i < NUM_GAMES; i++)
384             if (cur_games & (1u << i))
385                 draw_shadowed_text(STRING(GameName0 + i), MENU_GAMELIST_X + ((i % 2) * ((MFD_VIEW_WID - 10) / 2)),
386                                    MENU_GAMELIST_Y + ((i >> 1u) * MENU_GAMELIST_DY));
387     } else {
388         if (ms->save_time == TIME_TIL_SCREEN_SAVE) {
389             init_screen_save(ms);
390             ++ms->save_time;
391         }
392         // run screen saver
393         if ((rand() & 0xff) < 20) {
394             for (i = 0; i < 2; ++i) {
395                 ms->dx[i] = (rand() % 6) - 3;
396                 ms->dy[i] = (rand() % 6) - 3;
397                 if (ms->dx[i] >= 0)
398                     ms->dx[i] += 2;
399                 else
400                     ms->dx[i] -= 1;
401                 if (ms->dy[i] >= 0)
402                     ms->dy[i] += 2;
403                 else
404                     ms->dy[i] -= 1;
405             }
406         }
407         if ((rand() & 0xff) < 80)
408             ms->color = (rand() & 0x7f) + 32;
409         for (i = 0; i < 2; ++i) {
410             ms->x[i] += ms->dx[i];
411             ms->y[i] += ms->dy[i];
412             if (ms->x[i] < 0 || ms->x[i] >= MFD_VIEW_WID)
413                 ms->x[i] += (ms->dx[i] = -ms->dx[i]);
414             if (ms->y[i] < 0 || ms->y[i] >= MFD_VIEW_HGT)
415                 ms->y[i] += (ms->dy[i] = -ms->dy[i]);
416         }
417         old_lines[ss_head].x1 = ms->x[0];
418         old_lines[ss_head].y1 = ms->y[0];
419         old_lines[ss_head].x2 = ms->x[1];
420         old_lines[ss_head].y2 = ms->y[1];
421         old_lines[ss_head].c = ms->color++;
422         for (i = 0; i < MAX_LINES; ++i) {
423             gr_set_fcolor(old_lines[i].c);
424             ss_int_line(old_lines[i].x1, old_lines[i].y1, old_lines[i].x2, old_lines[i].y2);
425         }
426         if (++ss_head == MAX_LINES)
427             ss_head = 0;
428     }
429     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
430 }
431 
games_handle_menu(MFD * m,uiEvent * ev)432 uchar games_handle_menu(MFD *m, uiEvent *ev) {
433     uint32_t game;
434     uint32_t cur_games = player_struct.softs.misc[SOFTWARE_GAMES];
435 
436     if (GAME_MODE == GAME_MODE_MENU)
437         ((menu_state *)GAME_DATA)->save_time = 0;
438 
439     if (!(ev->mouse_data.action & MOUSE_LDOWN))
440         return FALSE; // ignore click releases
441 
442     if (ev->pos.y - m->rect.ul.y < MENU_GAMELIST_Y)
443         return FALSE;
444     game = (((ev->pos.y) - (m->rect.ul.y) - MENU_GAMELIST_Y) / MENU_GAMELIST_DY) * 2;
445     if ((ev->pos.x) - (m->rect.ul.x) > MENU_GAMELIST_X + (MFD_VIEW_WID - 10) / 2)
446         game++;
447 
448     if (game > NUM_GAMES || ((1u << game) & cur_games) == 0)
449         return FALSE;
450 
451     COOKIE = GAME_COOK(game);
452     LG_memset(GAME_DATA, 0, GAME_DATA_SIZE);
453     GAME_MODE = game;
454     game_init_funcs[GAME_MODE](GAME_DATA);
455 
456     string_message_info(REF_STR_GameDescrip0 + game);
457 
458     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
459     return TRUE;
460 }
461 
games_init_null(void * game_state)462 void games_init_null(void *game_state) { }
463 
games_expose_null(MFD * m,ubyte control)464 void games_expose_null(MFD *m, ubyte control) {
465     int cur_games = 0xff;
466 
467     if (!full_game_3d)
468         draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
469     ss_string(STRING(NotInstalled), 10, 20);
470     ss_string(STRING(NotInstalled + 1), 15, 35);
471     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
472 }
473 
games_handle_null(MFD * m,uiEvent * e)474 uchar games_handle_null(MFD *m, uiEvent *e) { return FALSE; }
475 
476     // -----------------
477     // mfd pong
478 
479 #define PLY_PADDLE_YRAD 2
480 #define PLY_PADDLE_XRAD 6
481 #define CMP_PADDLE_YRAD 2
482 #define CMP_PADDLE_XRAD 6
483 #define PONG_BALL_YRAD  2
484 #define PONG_BALL_XRAD  3
485 #define PONG_BORDER     3
486 #define PONG_SWEET_SPOT 2
487 
488 int get_new_x_dir(int paddle_loc, int ball_loc);
489 int generic_ball_and_paddle(void *game_state);
490 
491 // -------------------
492 // generic paddle-hits-ball code
493 
get_new_x_dir(int paddle_loc,int ball_loc)494 int get_new_x_dir(int paddle_loc, int ball_loc) {
495     int x_dir = ((ball_loc - paddle_loc) / 2);
496     if (x_dir < 0) {
497         if (x_dir >= -PONG_SWEET_SPOT)
498             x_dir = 0;
499         else if (x_dir < -PONG_X_DIR_MAX)
500             x_dir = -PONG_X_DIR_MAX;
501         else
502             x_dir++;
503     }
504     else if (x_dir > 0) {
505         if (x_dir <= PONG_SWEET_SPOT)
506             x_dir = 0;
507         else if (x_dir > PONG_X_DIR_MAX)
508             x_dir = PONG_X_DIR_MAX;
509         else
510             x_dir--;
511     }
512     return x_dir;
513 }
514 
515 // this function returns non-zero
516 // if the ball went off the bottom past the paddle
generic_ball_and_paddle(void * game_state)517 int generic_ball_and_paddle(void *game_state) {
518     pong_state *work_ps = (pong_state *)game_state;
519 
520     // now deal with moving the ball, assume it is currently a valid state
521     work_ps->ball_pos_x += work_ps->ball_dir_x;
522     work_ps->ball_pos_y += work_ps->ball_dir_y;
523     work_ps->p_pos += work_ps->p_spd;
524     if (work_ps->ball_pos_x < PONG_BALL_XRAD) {
525         work_ps->ball_pos_x = PONG_BALL_XRAD + (PONG_BALL_XRAD - work_ps->ball_pos_x);
526         work_ps->ball_dir_x = -work_ps->ball_dir_x;
527     }
528     if (work_ps->ball_pos_x > MFD_VIEW_WID - 1 - PONG_BALL_XRAD) {
529         work_ps->ball_pos_x =
530             MFD_VIEW_WID - 1 - PONG_BALL_XRAD - (PONG_BALL_XRAD - (MFD_VIEW_WID - 1 - work_ps->ball_pos_x));
531         work_ps->ball_dir_x = -work_ps->ball_dir_x;
532     }
533     if (work_ps->ball_dir_y >= 0)
534     // lets see whats up with the player
535     {
536         if (work_ps->ball_pos_y - PONG_BALL_YRAD > MFD_VIEW_HGT - PONG_BORDER) {
537             // out the bottom
538             return 1;
539         } else if (work_ps->ball_pos_y > MFD_VIEW_HGT - PONG_BORDER - CMP_PADDLE_YRAD * 2 - PONG_BALL_YRAD) {
540             // was the player there to deflect it
541             if ((work_ps->ball_pos_x + PONG_BALL_XRAD >= work_ps->p_pos - PLY_PADDLE_XRAD) &&
542                 (work_ps->ball_pos_x - PONG_BALL_XRAD <= work_ps->p_pos + PLY_PADDLE_XRAD)) {
543                 // we got it, larry
544                 int nxdir = get_new_x_dir(work_ps->p_pos, work_ps->ball_pos_x);
545                 // hmm, does dirx work yet
546                 work_ps->ball_dir_y = -work_ps->ball_dir_y;
547                 // average of reflection and new angle??? is this good?
548                 // minus looks good if it hits the edge of the paddle
549                 // plus looks good if it hits the middle of the paddle
550                 // hmm... so need average of reflection and new, but
551                 // reflection has different meanings depending where it hit
552                 work_ps->ball_dir_x = (nxdir - work_ps->ball_dir_x) >> 1u;
553                 play_digi_fx(SFX_MFD_SUCCESS, 1);
554             }
555         }
556     }
557     return 0;
558 }
559 
games_init_pong(void * game_state)560 void games_init_pong(void *game_state) {
561     pong_state *cur_ws = (pong_state *)game_state;
562     cur_ws->ball_dir_x = cur_ws->ball_dir_y = cur_ws->c_spd = cur_ws->p_spd = cur_ws->p_score = cur_ws->c_score =
563         cur_ws->game_won = 0;
564     cur_ws->c_pos = cur_ws->p_pos = MFD_VIEW_MID; // move paddles to middle
565     score_time = 0;
566     games_time_diff = 0;
567 }
568 
games_run_pong(pong_state * work_ps)569 void games_run_pong(pong_state *work_ps) {
570     int c_des_spd = 0;                                      // desired speed for the computer player
571     if ((work_ps->ball_dir_x | work_ps->ball_dir_y) == 0) { // create new ball
572         int serve_speed = (work_ps->c_score + work_ps->p_score) / 8 + 1;
573         work_ps->ball_pos_y = MFD_VIEW_HGT / 2;
574         //      work_ps->ball_dir_x=(rand()%3)-1;       // -1 -> 1 for x
575         work_ps->ball_dir_x = 0;
576         work_ps->ball_dir_y = ((rand() % 3) - 1) * serve_speed;
577         if (work_ps->ball_dir_y == 0)
578             work_ps->ball_dir_y = -serve_speed; // -2 -> 2, but not 0, for y
579         if (work_ps->ball_dir_y < 0)
580             work_ps->ball_pos_x = work_ps->c_pos;
581         else
582             work_ps->ball_pos_x = work_ps->p_pos;
583         score_time = 0;
584     }
585 
586     // the brutally powerful AI, thats right, AI, think about it
587     if (work_ps->ball_dir_y < 0) // moving towards computer
588     {
589         if (work_ps->c_pos < work_ps->ball_pos_x - CMP_PADDLE_XRAD) // we are too far left
590             c_des_spd = (((work_ps->ball_pos_x - work_ps->c_pos) >> 4u) + 1);
591         else if (work_ps->c_pos > work_ps->ball_pos_x + CMP_PADDLE_XRAD)
592             c_des_spd = -(((work_ps->c_pos - work_ps->ball_pos_x) >> 4u) + 1);
593     }
594     if (work_ps->c_spd > c_des_spd)
595         work_ps->c_spd--;
596     else if (work_ps->c_spd < c_des_spd)
597         work_ps->c_spd++;
598 
599     if (generic_ball_and_paddle(work_ps)) {
600         // the player missed.
601         // what a loser
602         play_digi_fx(SFX_MFD_BUZZ, 1);
603         work_ps->ball_dir_x = work_ps->ball_dir_y = 0;
604         work_ps->last_point = 0;
605         if (++work_ps->c_score == 0x7)
606             work_ps->game_won = 1;
607         score_time = player_struct.game_time;
608         return;
609     }
610 
611     work_ps->c_pos += work_ps->c_spd;
612     if (work_ps->ball_dir_y < 0) // moving towards computer
613     {
614         if (work_ps->ball_pos_y + PONG_BALL_YRAD < PONG_BORDER) { // out the top, point for the player
615             play_digi_fx(SFX_INVENT_WARE, 1);
616             work_ps->ball_dir_x = work_ps->ball_dir_y = 0;
617             work_ps->last_point = 1;
618             if (++work_ps->p_score == 0x7)
619                 work_ps->game_won = 1;
620             score_time = player_struct.game_time;
621         } else if (work_ps->ball_pos_y <
622                    PONG_BORDER + CMP_PADDLE_YRAD * 2 + PONG_BALL_YRAD) { // was the computer there to deflect it
623             if ((work_ps->ball_pos_x + PONG_BALL_XRAD >= work_ps->c_pos - CMP_PADDLE_XRAD) &&
624                 (work_ps->ball_pos_x - PONG_BALL_XRAD <= work_ps->c_pos + CMP_PADDLE_XRAD)) { // we got it, larry
625                 int nxdir = get_new_x_dir(work_ps->c_pos, work_ps->ball_pos_x);
626                 work_ps->ball_dir_y = -work_ps->ball_dir_y; // hmm, does dirx work yet
627                 work_ps->ball_dir_x =
628                     (nxdir - work_ps->ball_dir_x) >> 1u; // average of reflection and new angle??? is this good?
629                 work_ps->ball_dir_x += rand() % 3 - 1;
630                 play_digi_fx(SFX_MFD_SUCCESS, 1);
631             }
632         }
633     }
634 }
635 
636 #define PONG_CYCLE (CIT_CYCLE / 30)
games_expose_pong(MFD * m,ubyte control)637 void games_expose_pong(MFD *m, ubyte control) {
638     pong_state *cur_ps = (pong_state *)GAME_DATA;
639     int game_use = NORMAL_DISPLAY;
640 
641     if (control == 0 && score_time > 0)
642         score_time = 0;
643     // is not true
644     if (cur_ps->game_won) {
645         if (score_time + WIN_PAUSE > player_struct.game_time)
646             game_use = WIN_DISPLAY;
647         else
648         //       { games_init_pong(cur_ps); return;}
649         {
650             GAME_MODE = GAME_MODE_MENU;
651             return;
652         }
653     } else if (score_time + SCORE_PAUSE > player_struct.game_time) {
654         game_use = SCORE_DISPLAY;
655         games_time_diff = 0;
656     } else {
657         uiEvent fake_event;
658 
659         for (; games_time_diff >= PONG_CYCLE; games_time_diff -= PONG_CYCLE) {
660             games_run_pong(cur_ps);
661             ui_mouse_get_xy(&fake_event.pos.x, &fake_event.pos.y);
662             games_handle_pong(m, &fake_event);
663             if (score_time > 0 || cur_ps->game_won)
664                 break;
665         }
666     }
667     if (!full_game_3d)
668         draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
669 
670     gr_set_fcolor(ORANGE_YELLOW_BASE);
671     ss_rect(cur_ps->c_pos - CMP_PADDLE_XRAD, PONG_BORDER,
672         cur_ps->c_pos + CMP_PADDLE_XRAD, PONG_BORDER + CMP_PADDLE_YRAD * 2);
673 
674     gr_set_fcolor(ORANGE_YELLOW_BASE);
675     ss_rect(cur_ps->p_pos - PLY_PADDLE_XRAD, MFD_VIEW_HGT - PONG_BORDER - PLY_PADDLE_YRAD * 2,
676         cur_ps->p_pos + PLY_PADDLE_XRAD, MFD_VIEW_HGT - PONG_BORDER);
677 
678     gr_set_fcolor(GRAY_8_BASE);
679     ss_rect(cur_ps->ball_pos_x - PONG_BALL_XRAD, cur_ps->ball_pos_y - PONG_BALL_YRAD,
680         cur_ps->ball_pos_x + PONG_BALL_XRAD, cur_ps->ball_pos_y + PONG_BALL_YRAD);
681 
682     if (game_use != NORMAL_DISPLAY) {
683         char tmp[2] = "V";
684         if (cur_ps->last_point)
685             ss_string(STRING(YouHave), MFD_VIEW_MID - 18, 15);
686         else
687             ss_string(STRING(ComputerHas), MFD_VIEW_MID - 25, 15);
688         if (cur_ps->game_won)
689             ss_string(STRING(Won), MFD_VIEW_MID - 8, 22);
690         else
691             ss_string(STRING(Scored), MFD_VIEW_MID - 16, 22);
692         ss_string(STRING(You), MFD_VIEW_MID - 25, 35);
693         tmp[0] = '0' + cur_ps->p_score;
694         ss_string(tmp, MFD_VIEW_MID + 10, 35);
695         ss_string(STRING(Computer), MFD_VIEW_MID - 25, 45);
696         tmp[0] = '0' + cur_ps->c_score;
697         ss_string(tmp, MFD_VIEW_MID + 10, 45);
698     }
699     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
700 
701     // autoreexpose
702     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
703 
704 }
705 
706 #define PONG_FUDGE 10
707 
games_handle_pong(MFD * m,uiEvent * e)708 uchar games_handle_pong(MFD *m, uiEvent *e) {
709     pong_state *cur_ps = (pong_state *)GAME_DATA;
710     LGPoint pos = MakePoint(e->pos.x - m->rect.ul.x, e->pos.y - m->rect.ul.y);
711     ubyte spd = lg_min(3, abs(pos.x - cur_ps->p_pos) / PLY_PADDLE_XRAD + 1);
712     if (pos.x + PONG_FUDGE < 0 || pos.y + PONG_FUDGE < 0 || pos.x >= RectWidth(&m->rect) + PONG_FUDGE ||
713         pos.y >= RectHeight(&m->rect) + PONG_FUDGE) {
714         cur_ps->p_spd = 0;
715         return TRUE;
716     }
717     if (cur_ps->p_pos == pos.x)
718         cur_ps->p_spd = 0;
719     else if (cur_ps->p_pos > pos.x)
720         cur_ps->p_spd = -spd;
721     else
722         cur_ps->p_spd = spd;
723     return TRUE;
724 }
725 
726     //----------------------------
727     // mfd road game....
728 
729 #define NO_CAR    0u
730 #define BAD_CAR   1u
731 #define SMART_CAR 2u
732 #define CAR_MASK  3u
733 
734 #define NORMAL_DISPLAY 0
735 #define SCORE_DISPLAY  1
736 #define WIN_DISPLAY    2
737 
738 #define CAR_ROW 6u
739 #define car_hit() (cur_rs->lanes[cur_rs->player_lane] & (CAR_MASK << (CAR_ROW * 2)))
740 
741 #define HIT_BAD   (BAD_CAR << (CAR_ROW * 2))
742 #define HIT_SMART (SMART_CAR << (CAR_ROW * 2))
743 
744 #define FRAMES_PER      100
745 #define FRAMES_RAMPDOWN 10
746 
747 #define BASE_DIFF 5
748 #define DIFF_PER  4
749 #define DIFF_MUL  3
750 #define DIFF_MAX  13
751 #define LANE_MAX  8
752 #define GAME_WON  2
753 #define GAME_LOST 4
754 
755 #define LANE_WID    8u
756 #define LANE_HEIGHT 6u
757 #define LANE_ROWS   8u
758 #define ROAD_TOP    3u
759 #define CAR_TOP     1u
760 #define CAR_BOT     4u
761 #define ROAD_BOTTOM ROAD_TOP + (LANE_HEIGHT * LANE_ROWS)
762 
763 #define row_top(y) (ROAD_TOP + (LANE_HEIGHT * y))
764 
765 #define BRIGHT_LED      (RED_8_BASE)
766 #define DIM_LED         (RED_8_BASE + 5)
767 #define BRIGHT_PHOSPHOR (GREEN_8_BASE)
768 #define DIM_PHOSPHOR    (GREEN_8_BASE + 5)
769 #define BRIGHT_CAR      (BLUE_8_BASE)
770 #define DIM_CAR         (BLUE_8_BASE + 5)
771 
772 #define CAR_IDX 3
773 static uchar c_cols[][2] = {
774     {BRIGHT_LED, DIM_LED},
775     {BRIGHT_PHOSPHOR, DIM_PHOSPHOR},
776     {45, 61},
777     {BRIGHT_CAR, DIM_CAR}
778 };
779 
games_init_road(void * game_state)780 void games_init_road(void *game_state) {
781     road_state *cur_rs = (road_state *)GAME_DATA;
782     games_time_diff = 0;
783     cur_rs->lane_cnt = 3;
784     cur_rs->player_lane = 1;
785     cur_rs->diff = BASE_DIFF;
786     LG_memset(&cur_rs->player_move, 0, sizeof(road_state) - 4); // clear rest of fields
787 }
788 
games_run_road(road_state * s)789 void games_run_road(road_state *s) {
790     road_state *cur_rs = (road_state *)GAME_DATA;
791     int i;
792 
793     if (++cur_rs->frame_cnt == FRAMES_PER) // end of level set
794     {
795         cur_rs->diff += DIFF_MUL;
796         if (cur_rs->diff <= DIFF_MAX)
797             cur_rs->frame_cnt = 0;
798     }
799     for (i = 0; i < cur_rs->lane_cnt; i++) // update old cars
800         cur_rs->lanes[i] <<= 2;
801     if (cur_rs->frame_cnt > FRAMES_PER) {
802         if (cur_rs->frame_cnt > FRAMES_PER + FRAMES_RAMPDOWN) {
803             cur_rs->frame_cnt = 0;
804             cur_rs->diff = BASE_DIFF;
805             if (++cur_rs->lane_cnt > LANE_MAX) {
806                 cur_rs->game_state = GAME_WON;
807                 score_time = player_struct.game_time;
808             }
809         }
810         return;
811     }
812     for (i = 0; i < cur_rs->lane_cnt; i++) // new cars
813         if ((rand() % 0x3f) < cur_rs->diff) {
814             if ((rand() % 0x3f) == 5)
815                 cur_rs->lanes[i] |= SMART_CAR;
816             else
817                 cur_rs->lanes[i] |= BAD_CAR;
818         }
819     cur_rs->player_lane += cur_rs->player_move;
820     cur_rs->player_move = 0;
821     if (cur_rs->player_lane < 0)
822         cur_rs->player_lane = 0;
823     else if (cur_rs->player_lane >= cur_rs->lane_cnt)
824         cur_rs->player_lane = cur_rs->lane_cnt - 1;
825 
826     if (car_hit() == HIT_SMART)
827         LG_memset(cur_rs->lanes, 0, 8 * sizeof(ushort));
828     else if (car_hit()) // else if (car_hit()==HIT_BAD)
829     {
830         play_digi_fx(SFX_DESTROY_CRATE, 1);
831         cur_rs->game_state = GAME_LOST;
832         score_time = player_struct.game_time;
833     }
834 }
835 
road_vline(int x,int yt,int yb,int c1,int c2)836 static void road_vline(int x, int yt, int yb, int c1, int c2) {
837     gr_set_fcolor(c1);
838     ss_vline(x, yt, yb);
839     gr_set_fcolor(c2);
840     ss_vline(x - 1, yt, yb);
841     ss_vline(x + 1, yt, yb);
842 }
843 
844 #define ROAD_CYCLE (CIT_CYCLE / 5)
845 #define uiMakeaDerMotionEventenHausen uiMakeMotionEvent
games_expose_road(MFD * m,ubyte tac)846 void games_expose_road(MFD *m, ubyte tac) {
847     road_state *cur_rs = (road_state *)GAME_DATA;
848     int game_use = NORMAL_DISPLAY;
849 
850     if (tac == 0)
851         score_time = 0;
852     // is not true
853     if (cur_rs->game_state) {
854         if (score_time + WIN_PAUSE > player_struct.game_time)
855             game_use = WIN_DISPLAY;
856         else {
857             GAME_MODE = GAME_MODE_MENU;
858             return;
859         }
860     } else
861         for (; games_time_diff >= ROAD_CYCLE; games_time_diff -= ROAD_CYCLE) {
862             uiEvent fake_event;
863             uiMakeaDerMotionEventenHausen(&fake_event);
864             games_run_road(cur_rs);
865             games_handle_road(m, &fake_event);
866             if (cur_rs->game_state)
867                 break;
868         }
869 
870     if (!full_game_3d)
871         draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
872 
873     { // draw the game here, eh?
874         int ledge, i, j;
875         ledge = (MFD_VIEW_WID >> 1u) - ((LANE_WID >> 1u) * cur_rs->lane_cnt);
876         for (i = 0; i < cur_rs->lane_cnt; i++, ledge += LANE_WID) {
877             int msk = cur_rs->lanes[i];
878             road_vline(ledge, ROAD_TOP, ROAD_BOTTOM, c_cols[0][0], c_cols[0][1]);
879             for (j = 0; j < LANE_ROWS; j++, msk >>= 2u)
880                 if (msk & 0x3u)
881                     road_vline(ledge + (LANE_WID >> 1u), row_top(j) + CAR_TOP, row_top(j) + CAR_BOT,
882                                c_cols[(msk & 0x3u) - 1][0], c_cols[(msk & 0x3u) - 1][1]);
883             if (i == cur_rs->player_lane)
884                 road_vline(ledge + (LANE_WID >> 1u), row_top(CAR_ROW) + CAR_TOP, row_top(CAR_ROW) + CAR_BOT,
885                            c_cols[CAR_IDX][0], c_cols[CAR_IDX][1]);
886         }
887         road_vline(ledge, ROAD_TOP, ROAD_BOTTOM, c_cols[0][0], c_cols[0][1]);
888     }
889 
890     // won lost state
891     if (game_use != NORMAL_DISPLAY) {
892         gr_set_fcolor(GRAY_8_BASE);
893         ss_string(STRING(YouHave), MFD_VIEW_MID - 18, 15);
894         ss_string((cur_rs->game_state == GAME_WON ? STRING(Won) : STRING(Lost)), MFD_VIEW_MID - 8, 22);
895     }
896 
897     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
898     // autoreexpose
899     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
900 }
901 
games_handle_road(MFD * m,uiEvent * e)902 uchar games_handle_road(MFD *m, uiEvent *e) {
903 
904     if (e->type == UI_EVENT_MOUSE) {
905         road_state *cur_rs = (road_state *)GAME_DATA;
906         int bt = e->mouse_data.buttons;
907         if (bt != 0)
908             if (e->mouse_data.modifiers != 0)
909                 bt++;
910 
911         //	if (bt!=cur_rs->last_bs)
912         //	{
913         //		cur_rs->last_bs=bt;
914         // KLC - no need to worry with left/right handed mouse for Mac.
915         //		if (QUESTVAR_GET(MOUSEHAND_QVAR))
916         //			bt = (((me->buttons)&2)?1:0)+(((me->buttons)&1)?2:0);
917         if (bt == 1)
918             cur_rs->player_move = -1;
919         else if (bt == 2)
920             cur_rs->player_move = 1;
921     }
922     return TRUE;
923 }
924 
925     //----------------------------
926     // mfd Robot Invasion ("bots")
927     //
928     // cross between breakout and space invaders
929 
930 #define BOTS_NUM_ROWS    3u
931 #define BOTS_NUM_COLUMNS 8u
932 #define BOTS_MASK ((1u << BOTS_NUM_COLUMNS) - 1)
933 
934 #define BOT_WIDTH  8
935 #define BOT_HEIGHT 7
936 //#define BOT_TOP    10
937 
938 #define INVADER_TYPES 3
939 
940 static int invader[INVADER_TYPES] = {REF_IMG_RAsec1bot, REF_IMG_RAhopper, REF_IMG_RAservbot};
941 
bots_reset_ball(bots_state * bs)942 static void bots_reset_ball(bots_state *bs) {
943     bs->ball_pos_y = MFD_VIEW_HGT - PONG_BORDER - 2 * PONG_BALL_YRAD - 2 * PLY_PADDLE_YRAD;
944     bs->ball_pos_x = bs->p_pos;
945     bs->ball_dir_x = 0;
946     bs->ball_dir_y = -1;
947 }
948 
bots_reset_level(bots_state * bs)949 static void bots_reset_level(bots_state *bs) {
950     for (uint8_t i = 0; i < BOTS_NUM_ROWS; ++i)
951         bs->rows[i] = BOTS_MASK;
952     bs->hpos = 0;
953     bs->hspd = 1;
954     bs->vpos = 10;
955     bots_reset_ball(bs);
956 }
957 
games_init_bots(void * game_state)958 void games_init_bots(void *game_state) {
959     bots_state *bs = (bots_state *)game_state;
960 
961     bots_reset_level(bs);
962     bs->p_pos = MFD_VIEW_WID / 2;
963     bs->p_spd = 0;
964     bs->balls = 3;
965 }
966 
967 #define HPOS (bs->hpos >> 5)
968 #define BOT_TOP (bs->vpos)
969 
970 #define BOT_ON(x, y) \
971     ((x) >= 0 && (x) < BOTS_NUM_COLUMNS && (y) >= 0 && (y) < BOTS_NUM_ROWS && (bs->rows[y] & (1 << (x))))
972 #define CLEAR_BOT(x, y) (bs->rows[y] &= ~(1 << (x)))
973 
test_bot(bots_state * bs,int x,int y)974 static int test_bot(bots_state *bs, int x, int y) {
975     if (BOT_ON(x, y)) {
976         CLEAR_BOT(x, y);
977         if (bs->hspd > 0)
978             ++bs->hspd;
979         else
980             --bs->hspd;
981         return 1;
982     }
983     return 0;
984 }
985 
986 #define HPOS_HACK 3
987 
games_run_bots(bots_state * bs)988 void games_run_bots(bots_state *bs) {
989     int rev, guys;
990     uint8_t i;
991 
992     bs->hpos += bs->hspd;
993     // test if we've scrolled a bot off the left
994     // bot #x is at location hpos + x*BOT_WIDTH;
995     // so if say bot #0 is scrolled off, it's because
996     // hpos < 0; if bot #1, hpos < -BOT_WIDTH, etc.
997     // so if y = hpos/-BOT_WIDTH, that's how many bots
998     // we may have scrolled off.
999 
1000     guys = bs->rows[0] | bs->rows[1] | bs->rows[2];
1001 
1002     if (!guys) {
1003         bots_reset_level(bs);
1004         return;
1005     }
1006 
1007     for (i = 2; i >= 0; --i)
1008         if (bs->rows[i])
1009             break;
1010     if (BOT_TOP + BOT_HEIGHT * (i + 1) > MFD_VIEW_HGT - PONG_BORDER - 2 * PLY_PADDLE_YRAD)
1011         goto loser;
1012 
1013 #ifdef USE_BROKEN_CODE
1014     if (bs->hpos < HPOS_HACK) {
1015         rev = ((unsigned)(-HPOS - HPOS_HACK)) / BOT_WIDTH;
1016         rev = (1 << rev) - 1; // test bottommost bits
1017         if (guys & rev)
1018             bs->hpos += 2 * (bs->hspd = -bs->hspd);
1019     }
1020 #else
1021     if (bs->hpos < 0) {
1022         for (i = 0; i < BOTS_NUM_COLUMNS; ++i)
1023             if (guys & (1u << i))
1024                 break;
1025         if (i * BOT_WIDTH + HPOS - HPOS_HACK < 0) {
1026             bs->hpos += 2 * (bs->hspd = -bs->hspd);
1027             ++bs->vpos;
1028         }
1029     }
1030 #endif
1031 
1032     if (bs->hspd > 0) {
1033 #ifdef USE_BROKEN_CODE
1034         // position of the rightmost bot is BOTS_NUM_COLUMNS * BOT_WIDTH + hpos,
1035         // which scrolls off if > MFD_VIEW_WID
1036         rev = (MFD_VIEW_WID - HPOS) / BOT_WIDTH - BOTS_NUM_COLUMNS;
1037         // rev is now the number of bots we'd've shifted off the right
1038         if (rev > 0) {
1039             if (rev > BOTS_NUM_COLUMNS)
1040                 bs->hpos += 2 * (bs->hspd = -bs->hspd);
1041             else {
1042                 rev = ~((1 << (BOTS_NUM_COLUMNS - rev)) - 1);
1043                 if (guys & rev)
1044                     bs->hpos += 2 * (bs->hspd = -bs->hspd);
1045             }
1046         }
1047 #else
1048         // so loop through and see whether any bots are offscreen
1049         for (i = BOTS_NUM_COLUMNS - 1; i >= 0; --i)
1050             if (guys & (1u << i))
1051                 break;
1052         // i is the rightmost bot
1053         if (HPOS + i * BOT_WIDTH + 3 > MFD_VIEW_WID) {
1054             bs->hpos += 2 * (bs->hspd = -bs->hspd);
1055             ++bs->vpos;
1056         }
1057 #endif
1058     }
1059 
1060     if (generic_ball_and_paddle(bs)) {
1061         if (--bs->balls)
1062             bots_reset_ball(bs);
1063         else {
1064         loser:
1065             GAME_MODE = GAME_MODE_MENU;
1066             return;
1067         }
1068     } else {
1069         // handle other bouncy conditions
1070         // switch from upward to downward
1071         int bot_left = (bs->ball_pos_x - PONG_BALL_XRAD - HPOS) / BOT_WIDTH;
1072         int bot_right = (bs->ball_pos_x - PONG_BALL_XRAD - HPOS) / BOT_WIDTH;
1073         int bot_top = (bs->ball_pos_y - BOT_TOP - PONG_BALL_YRAD) / BOT_HEIGHT;
1074         int bot_bot = (bs->ball_pos_y - BOT_TOP + PONG_BALL_YRAD) / BOT_HEIGHT;
1075 
1076         if (bs->ball_dir_x == 0)
1077             bs->ball_dir_x = 1;
1078         rev = 0;
1079         if (bs->ball_dir_y < 0) {
1080             if (bs->ball_pos_y + PONG_BALL_YRAD < PONG_BORDER)
1081                 rev = 1;
1082             if (test_bot(bs, bot_left, bot_top) || test_bot(bs, bot_right, bot_top))
1083                 rev = 1;
1084         }
1085         if (bs->ball_dir_y > 0)
1086             if (test_bot(bs, bot_left, bot_bot) || test_bot(bs, bot_right, bot_bot))
1087                 rev = 1;
1088 
1089         if (rev)
1090             bs->ball_pos_y += (bs->ball_dir_y = -bs->ball_dir_y);
1091 
1092         rev = 0;
1093         if (bs->ball_dir_x < 0)
1094             if (test_bot(bs, bot_left, bot_top) || test_bot(bs, bot_left, bot_bot))
1095                 rev = 1;
1096         if (bs->ball_dir_x > 0)
1097             if (test_bot(bs, bot_right, bot_top) || test_bot(bs, bot_right, bot_bot))
1098                 rev = 1;
1099 
1100         if (rev)
1101             bs->ball_pos_x += (bs->ball_dir_x = -bs->ball_dir_x);
1102     }
1103 }
1104 
games_expose_bots(MFD * m,uchar control)1105 void games_expose_bots(MFD *m, uchar control) {
1106     bots_state *bs = (bots_state *)GAME_DATA;
1107     uiEvent fake_event;
1108 #ifdef SVGA_SUPPORT
1109     extern char convert_use_mode;
1110 #endif
1111 
1112     for (; games_time_diff >= PONG_CYCLE; games_time_diff -= PONG_CYCLE) {
1113         games_run_bots(bs);
1114         ui_mouse_get_xy(&fake_event.pos.x, &fake_event.pos.y);
1115         games_handle_pong(m, &fake_event);
1116     }
1117 
1118     if (!full_game_3d)
1119         draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
1120 
1121     for (uint8_t j = 0; j < BOTS_NUM_ROWS; ++j) {
1122         gr_set_fcolor(WHITE);
1123         for (uint8_t i = 0; i < BOTS_NUM_COLUMNS; ++i) {
1124             if (bs->rows[j] & (1u << i)) {
1125 #ifdef SVGA_SUPPORT
1126                 draw_res_bm_core(invader[j] + INVADER_TYPES * convert_use_mode, HPOS + BOT_WIDTH * i,
1127                                  BOT_HEIGHT * j + BOT_TOP - (j == 1), FALSE);
1128 #else
1129                 draw_res_bm(invader[j], HPOS + BOT_WIDTH * i, BOT_HEIGHT * j + BOT_TOP - (j == 1));
1130 #endif
1131             }
1132         }
1133     }
1134 
1135     gr_set_fcolor(ORANGE_YELLOW_BASE);
1136     ss_rect(bs->p_pos - PLY_PADDLE_XRAD, MFD_VIEW_HGT - PONG_BORDER - PLY_PADDLE_YRAD * 2, bs->p_pos + PLY_PADDLE_XRAD,
1137             MFD_VIEW_HGT - PONG_BORDER);
1138 
1139     gr_set_fcolor(GRAY_8_BASE);
1140     ss_rect(bs->ball_pos_x - PONG_BALL_XRAD, bs->ball_pos_y - PONG_BALL_YRAD, bs->ball_pos_x + PONG_BALL_XRAD,
1141             bs->ball_pos_y + PONG_BALL_YRAD);
1142 
1143     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
1144     // autoreexpose
1145     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
1146 
1147 }
1148 #undef HPOS
1149 
1150 //
1151 //--------------------
1152 // mfd missile command
1153 //
1154 // saved game state is merely the map of which guys are still alive,
1155 // which round number this is, the number of shots remaining, the number
1156 // of enemies remaining in the wave, and the state of the state machine.
1157 // Thus, when you restore, the state machine state forces us into the
1158 // right mode, and then you still have to fight the right enemies; but
1159 // you can cheat a bit by saving right before you might've lost a guy.
1160 // Not much we can do since save/restore events aren't visible to us.
1161 
1162 typedef struct {
1163     uchar game_mode, state;
1164 
1165     ushort enemies;
1166     ulong score;
1167 
1168     ushort level; // 12
1169 
1170     uchar guys; // 10           // guys & gun placements
1171 
1172     uchar lmissiles;
1173     uchar rmissiles; // 10
1174 
1175     uchar quarter;
1176     uchar bob;
1177 } mcom_state;
1178 
1179 // this macro should return a long (well, a 32-bit int) _lvalue_
1180 // (must be assignable) which is actually maintained even
1181 // when other games are played, i.e. it should be out of quest variables
1182 // and hey, look, it is.
1183 
1184 #define HISCORE_QVAR 0x2D
1185 
1186 #define HISCORE (*((ulong *)&player_struct.questvars[HISCORE_QVAR]))
1187 
1188 #define MAX_EXPLOSIONS 30
1189 #define MAX_ATTACKERS  24
1190 #define MAX_MISSILES   16
1191 
1192 #define EXPLODE_FRAMES 20
1193 #define FIRE_FRAMES    12
1194 
1195 // possible states for the state machine;
1196 // we explicitly encode pauses into the numbering scheme
1197 #define MCOM_WAIT_NEW_GAME 1
1198 #define MCOM_WAIT_FOR_LEVEL (MCOM_WAIT_NEW_GAME + 1)
1199 #define LEVEL_WAIT 30
1200 #define MCOM_PLAY_GAME (MCOM_WAIT_FOR_LEVEL + LEVEL_WAIT)
1201 #define MCOM_REPORT_MISSILES (MCOM_PLAY_GAME + 1)
1202 #define MISSILE_WAIT 16
1203 #define MCOM_REPORT_guys (MCOM_REPORT_MISSILES + MISSILE_WAIT)
1204 #define guy_WAIT 16
1205 #define MCOM_BONUS_guys (MCOM_REPORT_guys + guy_WAIT)
1206 #define BONUS_WAIT 8
1207 #define MCOM_RELEVEL (MCOM_BONUS_guys + BONUS_WAIT)
1208 
1209 // scoring
1210 #define SCORE_PER_MISSILE_KILLED 10
1211 #define SCORE_PER_LEFTOVER_SHOT 1
1212 #define SCORE_PER_GUY_ALIVE 100
1213 
1214 #define DIEGO_SCORE 5093
1215 
1216 static long shodan_score[7] = {1307496, 1259431, 1175035, 1143910, 1083477, 1032148, 1027869};
1217 
1218 #define EXPLODE (hideous_secret_game_storage + 4)
1219 
1220 typedef struct {
1221     uchar x;
1222     uchar y;
1223     uchar frame;
1224 } expStruct;
1225 expStruct *explode = (expStruct *)EXPLODE;
1226 
1227 #define ATTACKER (EXPLODE + sizeof(expStruct) * MAX_EXPLOSIONS)
1228 
1229 typedef struct {
1230     uchar sx;
1231     uchar sy;
1232     int x; // 1 bit sign, 8 bit integer, 8 bit fraction
1233     int y;
1234     int dx;
1235     int dy;
1236 } attackStruct;
1237 attackStruct *attack = (attackStruct *)ATTACKER;
1238 
1239 #define MISSILE (ATTACKER + sizeof(attackStruct) * MAX_ATTACKERS)
1240 
1241 typedef struct {
1242     uchar sx;
1243     uchar sy;
1244     uchar ex;
1245     uchar ey;
1246     uchar frame;
1247 } shotsStruct;
1248 shotsStruct *shots = (shotsStruct *)MISSILE;
1249 
1250 static int num_attackers, num_missiles, num_explosions;
1251 #define GROUND_TOP (4)
1252 #define guy_TOP (GROUND_TOP + 3)
1253 
1254 #define TRAIL_LENGTH 10
1255 
1256 static unsigned char radius[] = {1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 7, 6, 6, 6, 5, 5, 4, 3, 2, 1};
1257 
make_mcom_explode(int x,int y)1258 static void make_mcom_explode(int x, int y) {
1259     if (num_explosions < MAX_EXPLOSIONS) {
1260         explode[num_explosions].x = x;
1261         explode[num_explosions].y = y;
1262         explode[num_explosions++].frame = 0;
1263     }
1264 }
1265 
make_mcom_shot(int x,int y,int sx)1266 static void make_mcom_shot(int x, int y, int sx) {
1267     if (num_missiles < MAX_MISSILES) {
1268         shots[num_missiles].sx = sx;
1269         shots[num_missiles].sy = GROUND_TOP + 9;
1270         shots[num_missiles].ex = x;
1271         shots[num_missiles].ey = y;
1272         shots[num_missiles++].frame = 0;
1273         play_digi_fx(SFX_GUN_RAILGUN, 1);
1274     }
1275 }
1276 
1277 // if you make this a #define, make sure you cast it to
1278 // an int, otherwise it's unsigned and hoses the dx calculation
1279 
1280 // compute current vertical speed based on level
v_speed(void)1281 static int v_speed(void) { return 24 + ((mcom_state *)GAME_DATA)->level * 4; }
1282 
make_attacker(int sx,int sy,int dx)1283 static void make_attacker(int sx, int sy, int dx) {
1284     if (num_attackers < MAX_ATTACKERS && ((mcom_state *)GAME_DATA)->enemies) {
1285         attack[num_attackers].sx = sx;
1286         attack[num_attackers].sy = sy;
1287         attack[num_attackers].x = sx * 256;
1288         attack[num_attackers].y = sy * 256;
1289         attack[num_attackers].dy = -v_speed();
1290         attack[num_attackers++].dx = (dx - sx) * v_speed() / (sy - guy_TOP);
1291         ((mcom_state *)GAME_DATA)->enemies--;
1292     }
1293 }
1294 
guy_loc(int n)1295 static int guy_loc(int n) {
1296     // 0 1 2 3 4 5 6 7 8 9
1297     // |     ^     ^     |
1298     // edge  silo  silo  edge
1299 
1300     // so first map guy number 0..5 into above numbering scheme
1301     int k = (n / 2) * 3 + (n & 1) + 1;
1302 
1303     // then map 0 to 0 and 9 to MFD_VIEW_WID
1304     return MFD_VIEW_WID * k / 9;
1305 }
1306 
make_random_attacker(void)1307 static void make_random_attacker(void) {
1308     // attacker comes from anywhere on top, targetting any guy
1309     make_attacker(rand() % MFD_VIEW_WID, MFD_VIEW_HGT - 1, MFD_VIEW_WID * ((rand() % 8) + 1) / 9);
1310 }
1311 
1312 #define SQRD(x) ((x) * (x))
1313 
1314 #define BOAT_DEATH_NOISE SFX_SPARKING_CABLE
1315 #ifdef DEMO
1316 #define SWIMMER_DEATH_NOISE SFX_SPARKING_CABLE
1317 #else
1318 #define SWIMMER_DEATH_NOISE SFX_DEATH_9
1319 #endif
1320 
advance_mcom_state(void)1321 static void advance_mcom_state(void) {
1322     uint32_t i, j;
1323     uchar silo = FALSE;
1324     mcom_state *ms = (mcom_state *)GAME_DATA;
1325     uchar old_quarter = ms->quarter;
1326     uchar old_bob = ms->bob;
1327 
1328     // this code used to be in the expose func, hopefully this doesn't
1329     // break it
1330     switch (ms->state) {
1331     case MCOM_WAIT_NEW_GAME:
1332     case MCOM_PLAY_GAME:
1333         break;
1334     default:
1335         switch (++ms->state) {
1336         case MCOM_PLAY_GAME:
1337             mcom_start_level();
1338             break;
1339         case MCOM_RELEVEL:
1340             ms->state = MCOM_WAIT_FOR_LEVEL;
1341         }
1342     }
1343 
1344     // bob the swimmers
1345     ms->quarter = (player_struct.game_time / (CIT_CYCLE / 4)) & 3u;
1346     if (ms->quarter != old_quarter) {
1347         // bob swimmers down
1348         ms->bob |= rand() & ((1u << 6u) - 1);
1349         // bob old swimmers up
1350         ms->bob &= ~old_bob;
1351     }
1352 
1353     // advance explosion animations
1354     for (i = 0; i < num_explosions;)
1355         if (++(explode[i].frame) == EXPLODE_FRAMES)
1356             explode[i] = explode[--num_explosions];
1357         else
1358             ++i;
1359 
1360     // advance defense missiles steps... conveniently, all simply use
1361     // a frame counter.  if missile reaches end frame, blow it up
1362     for (i = 0; i < num_missiles;)
1363         if (++(shots[i].frame) >= FIRE_FRAMES) {
1364             make_mcom_explode(shots[i].ex, shots[i].ey);
1365             shots[i] = shots[--num_missiles];
1366         } else
1367             ++i;
1368 
1369     // advance an attacking missile.  if the missile is within range
1370     // of an explosion, kaboom!  otherwise, if it's down at ground level,
1371     // kaboom, and blow something up too!
1372     // otherwise just advance it
1373     for (i = 0; i < num_attackers;) {
1374         // check if we've gotten blown up by an explosion
1375         for (j = 0; j < num_explosions; ++j)
1376             if (SQRD((int)explode[j].x - (int)attack[i].x / 256) + SQRD((int)explode[j].y - (int)attack[i].y / 256) <=
1377                 SQRD(radius[explode[j].frame]))
1378                 break;
1379         if (j < num_explosions) {
1380             ms->score += SCORE_PER_MISSILE_KILLED;
1381         blowup:
1382             make_mcom_explode(attack[i].x / 256, attack[i].y / 256);
1383             attack[i] = attack[--num_attackers];
1384         } else {
1385             attack[i].x += attack[i].dx;
1386             attack[i].y += attack[i].dy;
1387             if (attack[i].y <= guy_TOP * 256) {
1388                 // blow up the guy that's here!
1389                 j = (attack[i].x / 256);
1390                 j = (9 * j + MFD_VIEW_WID / 2) / MFD_VIEW_WID - 1;
1391                 if (j == 2) {
1392                     ms->lmissiles = 0;
1393                     silo = TRUE;
1394                 }
1395                 if (j == 5) {
1396                     ms->rmissiles = 0;
1397                     silo = TRUE;
1398                 }
1399                 if (ms->guys & (1u << j))
1400                     play_digi_fx(silo ? BOAT_DEATH_NOISE : SWIMMER_DEATH_NOISE, 1);
1401                 else
1402                     play_digi_fx(SFX_SPARKING_CABLE, 1);
1403                 ms->guys &= ~(1u << j);
1404                 goto blowup;
1405             } else
1406                 ++i;
1407         }
1408     }
1409 }
1410 
1411 #define ALL_guys 0xffu  // binary 11111111
1412 #define LGUN_FLAG 0x04u // binary 00000100
1413 #define RGUN_FLAG 0x20u // binary 00100000
1414 #define JUST_guys (ALL_guys - LGUN_FLAG - RGUN_FLAG)
1415 
games_init_mcom(void * game_state)1416 static void games_init_mcom(void *game_state) {
1417     mcom_state *ms = (mcom_state *)game_state;
1418 
1419     ms->state = MCOM_WAIT_NEW_GAME;
1420     ms->guys = ALL_guys;
1421     num_attackers = num_missiles = num_explosions = 0;
1422     games_time_diff = 0;
1423 }
1424 
mcom_start_game(void)1425 static void mcom_start_game(void) {
1426     mcom_state *ms = (mcom_state *)GAME_DATA;
1427     ms->guys = ALL_guys;
1428     ms->state = MCOM_WAIT_FOR_LEVEL;
1429     ms->lmissiles = ms->rmissiles = 30;
1430     ms->level = 0;
1431     num_attackers = num_missiles = num_explosions = 0;
1432 }
1433 
mcom_start_level(void)1434 static void mcom_start_level(void) {
1435     int i;
1436     mcom_state *ms = (mcom_state *)GAME_DATA;
1437     ++ms->level;
1438     ms->enemies = 4 * (ms->level) / 3 + 15;
1439     ms->guys |= (LGUN_FLAG | RGUN_FLAG);
1440     ms->lmissiles = ms->rmissiles = 30;
1441     for (i = 0; i < (4 + (ms->level >> 3)); ++i)
1442         make_random_attacker();
1443 }
1444 
1445 // hey, hey, we got some user input
games_handle_mcom(MFD * m,uiEvent * e)1446 static uchar games_handle_mcom(MFD *m, uiEvent *e) {
1447     mcom_state *ms = (mcom_state *)GAME_DATA;
1448     LGPoint pos = MakePoint(e->pos.x - m->rect.ul.x, e->pos.y - m->rect.ul.y);
1449 
1450     if (ms->state == MCOM_WAIT_NEW_GAME) {
1451         if (e->mouse_data.action & MOUSE_LDOWN)
1452             mcom_start_game();
1453     } else if (ms->state < MCOM_PLAY_GAME || (ms->state == MCOM_PLAY_GAME && (ms->enemies || num_attackers))) {
1454         int left = (e->mouse_data.action & MOUSE_LDOWN) && (e->mouse_data.modifiers == 0);
1455         int right = (e->mouse_data.action & MOUSE_LDOWN) && (e->mouse_data.modifiers != 0);
1456         // KLC - no need to worry about left/right hand mouse for Mac version.
1457         //		if (QUESTVAR_GET(MOUSEHAND_QVAR))
1458         //		{
1459         //			int temp = left;
1460         //			left = right;
1461         //			right = temp;
1462         //		}
1463         if (left && ms->lmissiles)
1464             make_mcom_shot(pos.x, pos.y, MFD_VIEW_WID / 3), --ms->lmissiles;
1465         if (right && ms->rmissiles)
1466             make_mcom_shot(pos.x, pos.y, MFD_VIEW_WID * 2 / 3), --ms->rmissiles;
1467     }
1468     return TRUE;
1469 }
1470 
1471 // coordinates of the missiles in the boats
1472 static signed char ox[10] = {-3, -1, 1, 3, -2, 0, 2, -1, 1, 0};
1473 static signed char oy[10] = {1, 1, 1, 1, 3, 3, 3, 5, 5, 7};
1474 
draw_silo(int x,int num)1475 static void draw_silo(int x, int num) {
1476     // bottom row of silo must have room for 4 missiles, so 9 pixels wide
1477 
1478     draw_res_bm(REF_IMG_Destroyer, x - 4, GROUND_TOP - 2);
1479 
1480     // now plot the missiles waiting to fire
1481 
1482     if (!num)
1483         return;
1484     gr_set_fcolor(ORANGE_8_BASE + 1);
1485     num = num % 10;
1486     if (num == 0)
1487         num = 10;
1488     for (uint32_t i = 0; i < num; ++i)
1489         ss_rect(x + ox[i], GROUND_TOP + oy[i], x + ox[i] + 1, GROUND_TOP + oy[i] + 1);
1490 }
1491 
1492     // static unsigned char guy_disp[] = { 2,6,3,2,5 };
1493 
1494     //#define gr_int_cline(x0,y0,c0,x1,y1,c1) \
1495 //        gr_fix_cline(fix_make(x0,0),fix_make(y0,0),c0,\
1496 //                     fix_make(x1,0),fix_make(y1,0),c1)
1497     //           // convert 8-bit rgb values into grs_rgb
1498     //#define make_rgb(r,g,b) (((b) << 24) | ((g) << 13) | ((r) << 2))
1499 
1500 #define gr_int_cline(x0, y0, c0, x1, y1, c1) ss_int_line(x0, y0, x1, y1)
1501 
1502 // update ten times per second.
1503 #define MCOM_CYCLE (CIT_CYCLE / 35)
1504 
1505 static int hack[] = {0, SCORE_PER_GUY_ALIVE, SCORE_PER_GUY_ALIVE, SCORE_PER_GUY_ALIVE * 2};
games_expose_mcom(MFD * m,ubyte control)1506 static void games_expose_mcom(MFD *m, ubyte control) {
1507     uint32_t i;
1508     int32_t k;
1509     mcom_state *ms = (mcom_state *)GAME_DATA;
1510 
1511     if (!ms->state) {
1512         ms->state = MCOM_WAIT_NEW_GAME;
1513         ms->guys = ALL_guys;
1514     }
1515 
1516     // water
1517     gr_set_fcolor(BLUE_8_BASE + 6);
1518     ss_rect(0, GROUND_TOP + 1, MFD_VIEW_WID, MFD_VIEW_HGT);
1519 
1520     // sky
1521     gr_set_fcolor(AQUA_8_BASE + 3);
1522     ss_rect(0, 0, MFD_VIEW_WID, GROUND_TOP + 1);
1523 
1524     // print current score behind floating guys
1525     {
1526         char buffer[16];
1527         sprintf(buffer, "%06ld", ms->score);
1528         gr_set_fcolor(WHITE);
1529         ss_string(buffer, MFD_VIEW_WID - 5 * 6 + 4, 0);
1530     }
1531 
1532     if (ms->guys & LGUN_FLAG)
1533         draw_silo(MFD_VIEW_WID / 3, ms->lmissiles);
1534     if (ms->guys & RGUN_FLAG)
1535         draw_silo(MFD_VIEW_WID * 2 / 3, ms->rmissiles);
1536 
1537     // floating guys
1538     for (i = 0; i < 6; ++i)
1539         if ((1u << "\000\001\003\004\006\007"[i]) & ms->guys) {
1540             k = guy_loc(i);
1541             // now k is the center location of the guy, so now draw the guy
1542             draw_res_bm(REF_IMG_LittleGuy, k - 2, GROUND_TOP - 1 + (((1u << i) & ms->bob) != 0));
1543         }
1544 
1545     // we've drawn all the background, now draw the foreground stuff
1546 
1547     // draw foreground information behind everything,
1548     // just because it looks cool in Llamatron
1549     // but note we draw it in front of non-moving stuff
1550 
1551     gr_set_fcolor(AQUA_8_BASE + 3);
1552     if (ms->state == MCOM_WAIT_NEW_GAME) {
1553         for (i = 0; i < 8; ++i) {
1554             char buffer[16];
1555             if (HISCORE > DIEGO_SCORE) {
1556                 strncpy(buffer, player_struct.name, 8);
1557                 // limited space in hiscore display, so strncpy
1558                 strtoupper(buffer);
1559             }
1560 
1561             ss_string(i < 7 ? STRING(ShodanHiScore) : HISCORE <= DIEGO_SCORE ? STRING(DiegoHiScore) : buffer, 4,
1562                       i * 5 + 9);
1563             // Note that Shodan has scored 1 digit more than the authors of
1564             // Eel Zapper were expecting, so other people have a leading blank
1565             // of course it's totally unrealistic unless that this would work
1566             // out right unless their score-painting code printed from the
1567             // right, but that's not unreasonable since conversion to decimal
1568             // starts from the right.
1569             sprintf(buffer, i < 7 ? "%07ld" : "  %06ld",
1570                     i < 7 ? shodan_score[i] : HISCORE < DIEGO_SCORE ? DIEGO_SCORE : HISCORE);
1571             ss_string(buffer, MFD_VIEW_WID - 30, i * 5 + 9);
1572         }
1573         ss_string(STRING(ClickToPlay), MFD_VIEW_MID - 25, MFD_VIEW_HGT - 7);
1574     } else if (ms->state < MCOM_PLAY_GAME) {
1575         char buffer[16];
1576         sprintf(buffer, STRING(LevelNum), ms->level + 1);
1577         ss_string(buffer, MFD_VIEW_MID - 15, MFD_VIEW_HGT / 2);
1578     } else if (ms->state == MCOM_PLAY_GAME) {
1579     } else {
1580         // ideally this will countup how many you got
1581         char buffer[32];
1582         int z;
1583 
1584         z = ms->state - MCOM_REPORT_MISSILES;
1585         if (z > MISSILE_WAIT)
1586             z = MISSILE_WAIT;
1587         ss_string(STRING(DepthChargeBonus), 2, MFD_VIEW_HGT / 2 - 8);
1588         sprintf(buffer, "%d", (ms->lmissiles + ms->rmissiles) * SCORE_PER_LEFTOVER_SHOT * z / MISSILE_WAIT);
1589         ss_string(buffer, MFD_VIEW_MID - 5, MFD_VIEW_HGT / 2 - 3);
1590         z = ms->state - MCOM_REPORT_guys;
1591         if (z >= 0) {
1592             if (z > guy_WAIT)
1593                 z = guy_WAIT;
1594             ss_string(STRING(GuyBonus), 12, MFD_VIEW_HGT / 2 + 8);
1595             sprintf(buffer, "%d",
1596                     (hack[ms->guys & 3u] + hack[(ms->guys >> 3u) & 3u] + hack[(ms->guys >> 6u) & 3u]) * z / guy_WAIT);
1597             ss_string(buffer, MFD_VIEW_MID - 10, MFD_VIEW_HGT / 2 + 13);
1598         }
1599     }
1600 
1601     // draw the incoming missiles
1602     for (i = 0; i < num_attackers; ++i) {
1603         gr_set_fcolor(GREEN_8_BASE + 3);
1604         if (attack[i].y > (MFD_VIEW_HGT - TRAIL_LENGTH << 8))
1605             ss_fix_line(attack[i].x << 8u, attack[i].y << 8u, attack[i].sx << 16u, attack[i].sy << 16u);
1606         else
1607             ss_fix_line(attack[i].x << 8u, attack[i].y << 8u,
1608                 (attack[i].x + (attack[i].x - (attack[i].sx << 8u)) * TRAIL_LENGTH * 256 / (attack[i].y - (attack[i].sy << 8u))) << 8u,
1609                 (attack[i].y + (TRAIL_LENGTH << 8u)) << 8u);
1610 
1611         gr_set_fcolor(GRAY_8_BASE);
1612         // an incredibly stupid way to plot a pixel!  fix me
1613         ss_hline(attack[i].x / 256, attack[i].y / 256, attack[i].x / 256);
1614     }
1615 
1616     // draw the shots
1617 
1618     gr_set_fcolor(GREEN_BASE + 2);
1619     for (i = 0; i < num_missiles; ++i)
1620         draw_res_bm(REF_IMG_DepthCharge, shots[i].sx + (shots[i].ex - shots[i].sx) * shots[i].frame / FIRE_FRAMES,
1621                     shots[i].sy + (shots[i].ey - shots[i].sy) * shots[i].frame / FIRE_FRAMES);
1622 
1623     // draw all the explosions
1624 
1625     for (i = 0; i < num_explosions; ++i) {
1626         gr_set_fcolor(GRAY_8_BASE + 8 - radius[explode[i].frame]);
1627         ss_int_disk(explode[i].x, explode[i].y, radius[explode[i].frame] << 1u);
1628     }
1629 
1630     // advance the state of all the objects
1631 
1632     for (; games_time_diff >= MCOM_CYCLE; games_time_diff -= MCOM_CYCLE) {
1633         advance_mcom_state();
1634         if (ms->state == MCOM_PLAY_GAME) {
1635             if ((rand() % 1000) < (ms->level + 20))
1636                 make_random_attacker();
1637             if (!(ms->guys & JUST_guys)) {
1638                 ms->state = MCOM_WAIT_NEW_GAME;
1639                 if (ms->score > HISCORE)
1640                     HISCORE = ms->score;
1641             }
1642         }
1643     }
1644 
1645     // advance the game state if they're done
1646     if (ms->state == MCOM_PLAY_GAME && !ms->enemies && !num_explosions && !num_attackers && !num_missiles) {
1647         // bonuses
1648         ms->score += (ms->lmissiles + ms->rmissiles) * SCORE_PER_LEFTOVER_SHOT;
1649         ms->score += hack[ms->guys & 3u] + hack[(ms->guys >> 3u) & 3u] + hack[(ms->guys >> 6u) & 3u];
1650         ++ms->state;
1651     }
1652 
1653     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
1654     // autoreexpose
1655     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
1656 }
1657 
1658 #ifdef LOST_TREASURES_OF_MFD_GAMES
1659 //----------------------------
1660 //----------------------------
1661 // mfd 15-sliding-tile puzzle
1662 //----------------------------
1663 //----------------------------
1664 #define MFD_PUZZLE_SIZE     4
1665 #define MFD_PUZZLE_SQ       (MFD_PUZZLE_SIZE * MFD_PUZZLE_SIZE)
1666 #define PUZZ15_TILE_SIZE    (p15_styles[((puzzle15_state *)GAME_DATA)->style].tsize)
1667 #define PUZZ15_ULX          ((MFD_VIEW_WID - (MFD_PUZZLE_SIZE * PUZZ15_TILE_SIZE)) / 2)
1668 #define PUZZ15_ULY          ((MFD_VIEW_HGT - (MFD_PUZZLE_SIZE * PUZZ15_TILE_SIZE)) / 2)
1669 #define PUZZ15_CYCLE        ((CIT_CYCLE / 2) / PUZZ15_TILE_SIZE)
1670 #define PUZZ15_INIT_SCRAM   (4 * PUZZLE_DIFFICULTY + 5)
1671 #define PUZZ15_WID          (MFD_PUZZLE_SIZE * PUZZ15_TILE_SIZE)
1672 #define PUZZ15_HGT          (MFD_PUZZLE_SIZE * PUZZ15_TILE_SIZE)
1673 #define PUZZ15_AFRAME_CYCLE (CIT_CYCLE)
1674 #define NUM_PUZZ15_STYLES   5
1675 
1676 typedef struct {
1677     uchar game_mode;
1678     uchar tilenum[MFD_PUZZLE_SQ];
1679     uchar current_frame;
1680     uchar anim_source;
1681     uchar anim_dir;
1682     uchar style;
1683     uchar scramble;
1684     uchar movedto;
1685     uchar animframe;
1686     uchar pause;
1687 } puzzle15_state;
1688 
1689 typedef struct {
1690     uchar bcolor;
1691     uchar fcolor;
1692     Ref back;
1693     uchar numbers;
1694     uchar tsize;
1695     uchar animating;
1696 } puzz15_style;
1697 
1698 static puzz15_style p15_styles[NUM_PUZZ15_STYLES] = {
1699     {0x60, 0xB0, REF_IMG_EmailMugShotBase + 11, FALSE, 13, FALSE},
1700     {0x60, 0xB0, REF_IMG_EmailMugShotBase + 23, FALSE, 13, FALSE},
1701     {0xDE, 0x02, REF_IMG_TriopLogo15,           FALSE, 8,  FALSE},
1702     {0xDE, 0x02, REF_IMG_DiegoAnim15,           FALSE, 8,  TRUE},
1703     {0x4C, 0x01, 0,                             TRUE,  13, FALSE},
1704 };
1705 
games_init_15(void * game_state)1706 void games_init_15(void *game_state) {
1707     puzzle15_state *state = (puzzle15_state *)game_state;
1708     int i;
1709 
1710     for (i = 0; i < MFD_PUZZLE_SQ; i++)
1711         state->tilenum[i] = i + 1;
1712     state->tilenum[MFD_PUZZLE_SQ - 1] = 0;
1713     state->anim_source = MFD_PUZZLE_SQ;
1714     state->style = rand() % NUM_PUZZ15_STYLES;
1715     state->scramble = PUZZ15_INIT_SCRAM;
1716     games_time_diff = 0;
1717 }
1718 
puzz15_won()1719 static uchar puzz15_won() {
1720     puzzle15_state *st = (puzzle15_state *)GAME_DATA;
1721     int i;
1722 
1723     for (i = 0; i < MFD_PUZZLE_SQ - 1; i++) {
1724         if (st->tilenum[i] != (i + 1))
1725             return (FALSE);
1726     }
1727     return (TRUE);
1728 }
1729 
puzz15_xy(int ind,int * x,int * y)1730 static void puzz15_xy(int ind, int *x, int *y) {
1731     int r, c;
1732     r = ind / MFD_PUZZLE_SIZE;
1733     c = ind % MFD_PUZZLE_SIZE;
1734     *x = PUZZ15_ULX + (c * PUZZ15_TILE_SIZE);
1735     *y = PUZZ15_ULY + (r * PUZZ15_TILE_SIZE);
1736 }
1737 
puzz15_move(int x,int y)1738 static uchar puzz15_move(int x, int y) {
1739     puzzle15_state *st = (puzzle15_state *)GAME_DATA;
1740     int dir = -1, ind;
1741 
1742     ind = x + y * MFD_PUZZLE_SIZE;
1743 
1744     if (x > 0 && st->tilenum[ind - 1] == 0)
1745         dir = 3;
1746     else if (y > 0 && st->tilenum[ind - MFD_PUZZLE_SIZE] == 0)
1747         dir = 0;
1748     else if (x < MFD_PUZZLE_SIZE - 1 && st->tilenum[ind + 1] == 0)
1749         dir = 1;
1750     else if (y < MFD_PUZZLE_SIZE - 1 && st->tilenum[ind + MFD_PUZZLE_SIZE] == 0)
1751         dir = 2;
1752 
1753     if (dir == -1)
1754         return FALSE;
1755 
1756     st->anim_source = y * MFD_PUZZLE_SIZE + x;
1757     st->current_frame = 0;
1758     st->anim_dir = dir;
1759 
1760     return TRUE;
1761 }
1762 
games_expose_15(MFD * m,ubyte control)1763 void games_expose_15(MFD *m, ubyte control) {
1764     int i, x, y, t, dx, dy, dt;
1765     short sw, sh;
1766     uchar rex = FALSE;
1767     puzzle15_state *st = (puzzle15_state *)GAME_DATA;
1768     char buf[3];
1769     int cycle = PUZZ15_CYCLE, aframe;
1770     Ref back;
1771     uchar full, solv, nums = p15_styles[st->style].numbers;
1772 
1773     full = (control & MFD_EXPOSE_FULL);
1774 
1775     if (st->scramble > 0) {
1776         cycle /= 3;
1777         if (st->anim_source == MFD_PUZZLE_SQ) {
1778             do {
1779                 x = rand() % MFD_PUZZLE_SIZE;
1780                 y = rand() % MFD_PUZZLE_SIZE;
1781                 if ((x + y * MFD_PUZZLE_SIZE) != st->movedto)
1782                     rex = puzz15_move(x, y);
1783             } while (!rex);
1784             st->scramble--;
1785         }
1786     }
1787 
1788     if (p15_styles[st->style].animating) {
1789         rex = TRUE;
1790         aframe = (player_struct.game_time / PUZZ15_AFRAME_CYCLE) % 4;
1791         if (aframe != st->animframe) {
1792             st->animframe = aframe;
1793             full = TRUE;
1794         }
1795     }
1796 
1797     back = p15_styles[st->style].back;
1798     if (back)
1799         back += st->animframe;
1800     solv = st->pause && back;
1801     if (full) {
1802         if (!full_game_3d)
1803             draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
1804         gr_set_fcolor(0xBF);
1805         ss_rect(PUZZ15_ULX - 2, PUZZ15_ULY - 2, PUZZ15_ULX + PUZZ15_WID + 2, PUZZ15_ULY + PUZZ15_HGT + 2);
1806         mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
1807     }
1808     for (i = 0; i < MFD_PUZZLE_SQ; i++) {
1809         if (st->tilenum[i] == 0 && !(solv)) {
1810             puzz15_xy(i, &x, &y);
1811             gr_set_fcolor(0x1);
1812             ss_rect(x, y, x + PUZZ15_TILE_SIZE, y + PUZZ15_TILE_SIZE);
1813             mfd_add_rect(x, y, x + PUZZ15_TILE_SIZE, y + PUZZ15_TILE_SIZE);
1814         }
1815     }
1816     for (i = 0; i < MFD_PUZZLE_SQ; i++) {
1817         // draw background
1818         puzz15_xy(i, &x, &y);
1819         dx = dy = 0;
1820         t = st->tilenum[i];
1821         if (t != 0 || (solv)) {
1822             if (i == st->anim_source) {
1823                 switch (st->anim_dir) {
1824                 case 0:
1825                     dy = st->current_frame;
1826                     break;
1827                 case 1:
1828                     dx = -st->current_frame;
1829                     break;
1830                 case 2:
1831                     dy = -st->current_frame;
1832                     break;
1833                 case 3:
1834                     dx = st->current_frame;
1835                     break;
1836                 }
1837                 gr_set_fcolor(0x1);
1838                 ss_rect(x, y, x + PUZZ15_TILE_SIZE, y + PUZZ15_TILE_SIZE);
1839                 x -= dx;
1840                 y -= dy;
1841             }
1842             if (dx || dy || full) {
1843                 // draw background
1844                 gr_set_fcolor(0x1);
1845                 ss_rect(x + dx, y + dy, x + dx + PUZZ15_TILE_SIZE, y + dy + PUZZ15_TILE_SIZE);
1846                 mfd_add_rect(x + dx, y + dy, x + dx + PUZZ15_TILE_SIZE, y + dy + PUZZ15_TILE_SIZE);
1847                 gr_set_fcolor(p15_styles[st->style].bcolor + ((i + (i / MFD_PUZZLE_SIZE)) & 1));
1848                 ss_rect(x, y, x + PUZZ15_TILE_SIZE, y + PUZZ15_TILE_SIZE);
1849                 // draw pretty bitmap
1850                 if (back) {
1851                     int bx, by, bw, bh;
1852                     ss_safe_set_cliprect(x, y, x + PUZZ15_TILE_SIZE, y + PUZZ15_TILE_SIZE);
1853                     puzz15_xy((t == 0 ? MFD_PUZZLE_SQ : t) - 1, &bx, &by);
1854                     bw = res_bm_width(back);
1855                     bh = res_bm_height(back);
1856                     draw_res_bm(back, PUZZ15_ULX + x - bx + (PUZZ15_WID - bw) / 2,
1857                                 PUZZ15_ULY + y - by + (PUZZ15_HGT - bh) / 2);
1858                     ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
1859                 }
1860                 // draw number
1861                 if (nums) {
1862                     gr_set_fcolor(p15_styles[st->style].fcolor);
1863                     sprintf(buf, "%d", t);
1864                     gr_string_size(buf, &sw, &sh);
1865                     sw--; // assume blank pixel of kerning.
1866                     sh--; // assume pixel descender.
1867                     ss_string(buf, x + (PUZZ15_TILE_SIZE - sw) / 2, y + (PUZZ15_TILE_SIZE - sh) / 2);
1868                 }
1869                 mfd_add_rect(x, y, x + PUZZ15_TILE_SIZE, y + PUZZ15_TILE_SIZE);
1870             }
1871         }
1872     }
1873 
1874     rex = rex || (st->anim_source < MFD_PUZZLE_SQ);
1875     for (; games_time_diff >= cycle; games_time_diff -= cycle) {
1876         if (st->anim_source < MFD_PUZZLE_SQ) {
1877             if (st->current_frame == PUZZ15_TILE_SIZE) {
1878                 st->current_frame = 0;
1879                 dt = (st->anim_dir & 1u) ? 1 : -MFD_PUZZLE_SIZE;
1880                 if (st->anim_dir > 1)
1881                     dt = -dt;
1882                 st->movedto = st->anim_source + dt;
1883                 st->tilenum[st->movedto] = st->tilenum[st->anim_source];
1884                 st->tilenum[st->anim_source] = 0;
1885                 st->anim_source = MFD_PUZZLE_SQ;
1886                 mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
1887                 if (puzz15_won()) {
1888                     st->pause = TRUE;
1889                     return;
1890                 }
1891             } else
1892                 st->current_frame++;
1893         }
1894     }
1895 
1896     // autoreexpose
1897     if (rex)
1898         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
1899 }
1900 
games_handle_15(MFD * m,uiEvent * e)1901 uchar games_handle_15(MFD *m, uiEvent *e) {
1902     puzzle15_state *st = (puzzle15_state *)GAME_DATA;
1903     LGPoint pos = MakePoint(e->pos.x - m->rect.ul.x - PUZZ15_ULX, e->pos.y - m->rect.ul.y - PUZZ15_ULY);
1904 
1905     if (st->scramble > 0)
1906         return FALSE;
1907 
1908     if (!(e->mouse_data.action & MOUSE_LDOWN))
1909         return FALSE;
1910 
1911     if (pos.x < 0 || pos.y < 0)
1912         return TRUE;
1913 
1914     pos.x /= PUZZ15_TILE_SIZE;
1915     pos.y /= PUZZ15_TILE_SIZE;
1916 
1917     if (pos.x >= MFD_PUZZLE_SIZE || pos.y >= MFD_PUZZLE_SIZE)
1918         return TRUE;
1919     if (st->pause) {
1920         st->pause = FALSE;
1921         st->scramble = PUZZ15_INIT_SCRAM;
1922         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
1923         return TRUE;
1924     }
1925     if (st->anim_source < MFD_PUZZLE_SQ)
1926         return TRUE;
1927 
1928     if (puzz15_move(pos.x, pos.y))
1929         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
1930 
1931     return TRUE;
1932 }
1933 
1934 //----------------------------
1935 //----------------------------
1936 // mfd Tic-Tac-Toe
1937 //----------------------------
1938 //----------------------------
1939 
1940 typedef struct {
1941     uchar owner[9];
1942 } tictactoe;
1943 
1944 typedef struct {
1945     uchar game_mode;
1946     tictactoe board;
1947     uchar whomoves;
1948     uchar whoplayer;
1949 } ttt_state;
1950 
1951 #define NOBODY 0
1952 #define X 1
1953 #define O 2
1954 #define OTHERPLAYER(w) (X + O - (w))
1955 
1956 #define TTT_SQ_WID 20
1957 #define TTT_SQ_HGT 16
1958 
1959 #define TTT_ULX      ((MFD_VIEW_WID - (3 * TTT_SQ_WID)) / 2)
1960 // this leaves a little room at the top for text, which
1961 // we secretly declare is 6 pixels
1962 #define TTT_MESS_HGT 6
1963 #define TTT_ULY      (TTT_MESS_HGT + (MFD_VIEW_HGT - TTT_MESS_HGT - (3 * TTT_SQ_HGT)) / 2)
1964 #define TTT_PUZ_WID  (3 * TTT_SQ_WID)
1965 #define TTT_PUZ_HGT  (3 * TTT_SQ_HGT)
1966 #define TTT_LRX      (TTT_ULX + TTT_PUZ_WID)
1967 #define TTT_LRY      (TTT_ULY + TTT_PUZ_HGT)
1968 
1969 static void tictactoe_drawwin(ttt_state *st);
1970 uchar tictactoe_generator(void *pos, int index, bool minimizer_moves);
1971 
1972 // ----------------------
1973 // TIC-TAC-TOE:
1974 //   static evaluator, move generator
1975 // ----------------------
1976 
winnerval(uchar owner)1977 static int winnerval(uchar owner) {
1978     if (owner == X)
1979         return INT_MAX;
1980     else if (owner == O)
1981         return INT_MIN;
1982     else
1983         return 0;
1984 }
1985 
tictactoe_over(tictactoe * st)1986 static uchar tictactoe_over(tictactoe *st) {
1987     int i, val;
1988 
1989     val = tictactoe_evaluator(st);
1990     if (val == winnerval(X) || val == winnerval(O))
1991         return TRUE;
1992 
1993     for (i = 0; i < 9; i++) {
1994         if (st->owner[i] == NOBODY)
1995             return FALSE;
1996     }
1997     return TRUE;
1998 }
1999 
2000 static char corners_ttt[] = {0, 2, 6, 8};
2001 
games_init_ttt(void * game_state)2002 void games_init_ttt(void *game_state) {
2003     ttt_state *state = (ttt_state *)game_state;
2004 
2005     state->whomoves = X;
2006     state->whoplayer = (rand() & 1) ? X : O;
2007 
2008     fstack_init(hideous_secret_game_storage + sizeof(ttt_state),
2009                 sizeof(hideous_secret_game_storage) - sizeof(ttt_state));
2010     if (state->whoplayer != state->whomoves) {
2011         // fake straight to a corner move
2012         state->board.owner[corners_ttt[rand() & 3]] = state->whomoves;
2013         state->whomoves = state->whoplayer;
2014     }
2015 }
2016 
2017 static char initmove_ttt[] = {0, 1, 4};
2018 
ttt_fullness(tictactoe * st)2019 static int ttt_fullness(tictactoe *st) {
2020     int i, ret = 0;
2021 
2022     for (i = 0; i < 9; i++) {
2023         if (st->owner[i] != NOBODY)
2024             ret++;
2025     }
2026     return ret;
2027 }
2028 
move_to_index(char move,tictactoe * st)2029 static char move_to_index(char move, tictactoe *st) {
2030     int i;
2031     uchar empty;
2032 
2033     empty = (ttt_fullness(st) == 0);
2034     if (empty)
2035         return initmove_ttt[move];
2036 
2037     for (i = 0; i < 9 && move >= 0; i++) {
2038         if (st->owner[i] == NOBODY) {
2039             if (move == 0)
2040                 return i;
2041             move--;
2042         }
2043     }
2044     return -1;
2045 }
2046 
games_expose_ttt(MFD * m,ubyte control)2047 void games_expose_ttt(MFD *m, ubyte control) {
2048     uchar full;
2049     int val;
2050     static long timeformove = 0, dt, timeout;
2051     char whichmove;
2052     ttt_state *st = (ttt_state *)GAME_DATA;
2053     int loops = 0;
2054 
2055     full = (control & MFD_EXPOSE_FULL);
2056 
2057     if (full) {
2058         int x, y;
2059         uchar over;
2060         uchar owner;
2061         Ref bm;
2062 
2063         if (!full_game_3d)
2064             draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
2065         gr_set_fcolor(RED_8_BASE + 4);
2066         over = tictactoe_over(&(st->board));
2067         if (!over) {
2068             // note that we are shamelessly using "bm" to temporarily
2069             // house a string.  Sue me.
2070             if (st->whomoves == st->whoplayer)
2071                 bm = REF_STR_YourMove;
2072             else
2073                 bm = REF_STR_Thinking;
2074             draw_shadowed_text(get_temp_string(bm), (MFD_VIEW_WID - gr_string_width(get_temp_string(bm))) / 2, 1);
2075         }
2076         // Hmm, this used to be gr_uvline.... maybe insufficent to just make it clipped.
2077         ss_vline(TTT_ULX + TTT_SQ_WID, TTT_ULY, TTT_ULY + TTT_PUZ_HGT);
2078         ss_vline(TTT_ULX + 2 * TTT_SQ_WID, TTT_ULY, TTT_ULY + TTT_PUZ_HGT);
2079         ss_int_line(TTT_ULX, TTT_ULY + TTT_SQ_HGT, TTT_ULX + TTT_PUZ_WID, TTT_ULY + TTT_SQ_HGT);
2080         ss_int_line(TTT_ULX, TTT_ULY + 2 * TTT_SQ_HGT, TTT_ULX + TTT_PUZ_WID, TTT_ULY + 2 * TTT_SQ_HGT);
2081         for (y = 0; y < 3; y++) {
2082             for (x = 0; x < 3; x++) {
2083                 owner = st->board.owner[x + 3 * y];
2084                 if (owner == NOBODY)
2085                     bm = ID_NULL;
2086                 else if (owner == st->whoplayer)
2087                     bm = REF_IMG_ttt_Player;
2088                 else
2089                     bm = REF_IMG_ttt_Shodan;
2090                 if (bm != ID_NULL)
2091                     draw_res_bm(bm, 1 + TTT_ULX + TTT_SQ_WID * x, 1 + TTT_ULY + TTT_SQ_HGT * y);
2092             }
2093         }
2094         if (over) {
2095             gr_set_fcolor(BLUE_8_BASE + 2);
2096             tictactoe_drawwin(st);
2097         }
2098         mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
2099     }
2100     if (st->whomoves == st->whoplayer)
2101         return;
2102     if (tictactoe_over(&(st->board)))
2103         return;
2104 
2105     // give us enough time to still achieve 15 fps
2106     dt = (CIT_CYCLE / 15) - player_struct.deltat;
2107     // or give us at least 1/55 second per frame.
2108     if (dt < (CIT_CYCLE / 55))
2109         dt = CIT_CYCLE / 55;
2110 
2111     timeformove += dt;
2112 
2113     if (timeformove <= 0)
2114         return;
2115 
2116     timeout = *tmd_ticks + timeformove;
2117 
2118     while (*tmd_ticks < timeout) {
2119         minimax_step();
2120         loops++;
2121         if (minimax_done())
2122             timeout = *tmd_ticks;
2123     }
2124     timeformove = timeout - *tmd_ticks;
2125 
2126     if (minimax_done()) {
2127         minimax_get_result(&val, &whichmove);
2128         st->board.owner[move_to_index(whichmove, &(st->board))] = OTHERPLAYER(st->whoplayer);
2129         st->whomoves = st->whoplayer;
2130         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
2131     } else
2132         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
2133 }
2134 
tictactoe_evaluator(void * pos)2135 int tictactoe_evaluator(void *pos) {
2136     tictactoe *t = (tictactoe *)pos;
2137     uchar win;
2138 
2139     win = t->owner[0];
2140     if (win != NOBODY) {
2141         if (t->owner[1] == win && t->owner[2] == win)
2142             return winnerval(win);
2143         if (t->owner[3] == win && t->owner[6] == win)
2144             return winnerval(win);
2145     }
2146 
2147     win = t->owner[8];
2148     if (win != NOBODY) {
2149         if (t->owner[6] == win && t->owner[7] == win)
2150             return winnerval(win);
2151         if (t->owner[2] == win && t->owner[5] == win)
2152             return winnerval(win);
2153     }
2154 
2155     win = t->owner[4];
2156     if (win != NOBODY) {
2157         if (t->owner[3] == win && t->owner[5] == win)
2158             return winnerval(win);
2159         if (t->owner[1] == win && t->owner[7] == win)
2160             return winnerval(win);
2161         if (t->owner[0] == win && t->owner[8] == win)
2162             return winnerval(win);
2163         if (t->owner[6] == win && t->owner[2] == win)
2164             return winnerval(win);
2165     }
2166 
2167     return winnerval(NOBODY);
2168 }
2169 
2170 // note that this procedure duplicates a lot of the work done by
2171 // tictactoe_evaluator: we do not consolidate them because we don't
2172 // want to slow down the evaluator.
tictactoe_drawwin(ttt_state * st)2173 void tictactoe_drawwin(ttt_state *st) {
2174     uchar win, realwin;
2175     int i;
2176     LGPoint p1, p2;
2177     char buf[80];
2178     tictactoe *t = &(st->board);
2179 
2180     p1.x = -1;
2181 
2182     for (i = 0; i < 3; i++) {
2183         win = t->owner[i];
2184         if (t->owner[i + 3] == win && t->owner[i + 6] == win) {
2185             realwin = win;
2186             p1.x = TTT_ULX + (TTT_SQ_WID * i) + (TTT_SQ_WID / 2);
2187             p1.y = TTT_ULY;
2188             p2.x = p1.x;
2189             p2.y = TTT_LRY;
2190         }
2191     }
2192     for (i = 0; i < 9; i += 3) {
2193         win = t->owner[i];
2194         if (t->owner[i + 1] == win && t->owner[i + 2] == win) {
2195             realwin = win;
2196             p1.x = TTT_ULX;
2197             p1.y = TTT_ULY + (TTT_SQ_HGT * i / 3) + (TTT_SQ_HGT / 2);
2198             p2.x = TTT_LRX;
2199             p2.y = p1.y;
2200         }
2201     }
2202     win = t->owner[0];
2203     if (t->owner[4] == win && t->owner[8] == win) {
2204         realwin = win;
2205         p1.x = TTT_ULX;
2206         p1.y = TTT_ULY;
2207         p2.x = TTT_LRX;
2208         p2.y = TTT_LRY;
2209     }
2210     win = t->owner[6];
2211     if (t->owner[4] == win && t->owner[2] == win) {
2212         realwin = win;
2213         p1.x = TTT_ULX;
2214         p1.y = TTT_LRY;
2215         p2.x = TTT_LRX;
2216         p2.y = TTT_ULY;
2217     }
2218     if (p1.x > 0) {
2219         ss_int_line(p1.x, p1.y, p2.x, p2.y);
2220         sprintf(buf, "%s%s",
2221             realwin == st->whoplayer ? (char *)RefGet(REF_STR_YouHave) : (char *)RefGet(REF_STR_ComputerHas),
2222             (char *)RefGet(REF_STR_Won));
2223         draw_shadowed_text(buf, MFD_VIEW_WID - gr_string_width(buf) - 1, 1);
2224     }
2225 }
2226 
tictactoe_generator(void * pos,int index,bool minimizer_moves)2227 uchar tictactoe_generator(void *pos, int index, bool minimizer_moves) {
2228     tictactoe *t = (tictactoe *)pos;
2229     uchar empty = TRUE;
2230     uchar mover = minimizer_moves ? O : X;
2231 
2232     int realindex = index;
2233 
2234     if (tictactoe_evaluator(pos) != winnerval(NOBODY))
2235         return FALSE; // already have a winner => no children
2236 
2237 #define NO_SYMMETRIES
2238 #ifdef NO_SYMMETRIES
2239     for (uint8_t i = 0; empty && i < 9; i++) {
2240         if (t->owner[i] != NOBODY)
2241             empty = FALSE;
2242     }
2243 
2244     // don't bother with symmetries of starting moves
2245     if (empty) {
2246         switch (index) {
2247         case 0:
2248             t->owner[0] = mover;
2249             return TRUE;
2250         case 1:
2251             t->owner[1] = mover;
2252             return TRUE;
2253         case 2:
2254             t->owner[4] = mover;
2255             return TRUE;
2256         default:
2257             return FALSE;
2258         }
2259     }
2260 #endif
2261 
2262     for (uint8_t i = 0; i < 9; i++) {
2263         if (t->owner[i] == NOBODY) {
2264             if (index == 0) {
2265                 t->owner[i] = mover;
2266                 return TRUE;
2267             }
2268             index--;
2269         }
2270     }
2271     return FALSE;
2272 }
2273 
games_handle_ttt(MFD * m,uiEvent * e)2274 uchar games_handle_ttt(MFD *m, uiEvent *e) {
2275     ttt_state *st = (ttt_state *)GAME_DATA;
2276     LGPoint pos = MakePoint(e->pos.x - m->rect.ul.x - TTT_ULX, e->pos.y - m->rect.ul.y - TTT_ULY);
2277 
2278     if (!(e->mouse_data.action & MOUSE_LDOWN))
2279         return FALSE;
2280     if (st->whomoves != st->whoplayer)
2281         return TRUE;
2282     if (tictactoe_over(&(st->board)))
2283         return TRUE;
2284     if (pos.x < 0 || pos.y < 0)
2285         return TRUE;
2286 
2287     pos.x /= TTT_SQ_WID;
2288     pos.y /= TTT_SQ_HGT;
2289 
2290     if (pos.x >= TTT_PUZ_WID || pos.y >= TTT_PUZ_HGT)
2291         return TRUE;
2292 
2293     if (st->board.owner[pos.x + 3 * pos.y] != NOBODY)
2294         return TRUE;
2295 
2296     st->board.owner[pos.x + 3 * pos.y] = st->whoplayer;
2297 
2298     if (!tictactoe_over(&(st->board))) {
2299         st->whomoves = OTHERPLAYER(st->whoplayer);
2300 
2301         minimax_setup(&(st->board), sizeof(tictactoe), 9, st->whomoves == O, tictactoe_evaluator, tictactoe_generator,
2302                       NULL);
2303     }
2304 
2305     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, TRUE);
2306 
2307     return TRUE;
2308 }
2309 
2310     //
2311     //   MFD Wing Commander
2312     //
2313     // use the position of the mouse to steer
2314     //   button 1 makes you fire; button 2 lets
2315     //   you control thrust and rotate view
2316     // always fire towards middle
2317 
2318     // Object structure... this handles arbitrary object in 3d
2319 
2320     // We'll always track all objects in arbitrary player-centric
2321     // viewspace, a la Star Raiders
2322 
2323 #define WING_QUEST_VAR 0x38
2324 
2325 #define WING_SFX_GOODGUY_FIRE SFX_GUN_SKORPION
2326 #define WING_SFX_BADGUY_FIRE  SFX_GUN_STUNGUN
2327 
2328 #define WING_SFX_HIT_PLAYER   SFX_METAL_SPANG
2329 #define WING_SFX_HIT_OTHER    SFX_GUN_PIPE_HIT_METAL
2330 
2331 #define WING_SFX_EXPLODE      SFX_CPU_EXPLODE
2332 
2333 #define WING_SFX_COMM         SFX_MFD_SUCCESS
2334 #define WING_SFX_AUTOPILOT    SFX_SHIELD_UP
2335 #define WING_SFX_THEYRE_ATTACKING_US_SIR 113
2336 
2337 #define WING_HEAR_EXPLODE 512
2338 #define WING_HEAR_HIT     256
2339 #define WING_HEAR_FIRE    192
2340 
2341 typedef struct {
2342     fix x, y, z;    // coordinates in 3space
2343     fix dx, dy, dz; // velocity in 3space for objects
2344     int type;       // what typa object is it
2345     int damage;     // how much damage 'til it blows
2346 } wing_obj;
2347 
2348 typedef struct {
2349     fix x, y, z;
2350     int color;
2351 } wing_star;
2352 
2353 #define MAX_WING_OBJECTS ((HIDEOUS_GAME_STORAGE - 512) / sizeof(wing_obj))
2354 #define MAX_WING_STARS (512 / sizeof(wing_star))
2355 
2356 wing_obj *wing = (wing_obj *)(hideous_secret_game_storage + 4);
2357 wing_star *wing_st = (wing_star *)(hideous_secret_game_storage + HIDEOUS_GAME_STORAGE - 512 + 4);
2358 
2359 // so I note that all of these variables should be in the
2360 // player struct, that is in the mfd game state, and I'll
2361 // fix that later.
2362 enum WingmanMode { WINGMAN_FORMATION, WINGMAN_ATTACK };
2363 
2364 #if 0
2365 
2366 static int WingmanMode wingman_mode = WINGMAN_FORMATION;
2367 static int num_wing_objects, wing_frame_count;
2368 static int wing_game_mode = WING_BRIEFING, wing_level;
2369 static int wing_message, wing_message_timer;
2370 
2371 #else
2372 
2373 struct wing_data {
2374     uchar game_mode;
2375     uchar wd_wingman_mode;
2376     uchar wd_num_wing_objects;
2377     uchar wd_wing_frame_count;
2378     uchar wd_wing_game_mode;
2379     uchar wd_wing_level;
2380     uchar wd_wing_message;
2381     uchar wd_wing_message_timer;
2382 };
2383 
2384 #define WING_DATA ((struct wing_data *)GAME_DATA)
2385 
2386 #define wingman_mode       (WING_DATA->wd_wingman_mode)
2387 #define num_wing_objects   (WING_DATA->wd_num_wing_objects)
2388 #define wing_frame_count   (WING_DATA->wd_wing_frame_count)
2389 #define wing_game_mode     (WING_DATA->wd_wing_game_mode)
2390 #define wing_level         (WING_DATA->wd_wing_level)
2391 #define wing_message       (WING_DATA->wd_wing_message)
2392 #define wing_message_timer (WING_DATA->wd_wing_message_timer)
2393 
2394 #endif
2395 
2396 #ifdef PLAYTEST
2397 static int wing_cheat = 0;
2398 #endif
2399 
2400 enum WingTypes { WING_BLUE_HAIR, WING_SHOT, WING_BOOM, WING_WINGMAN, WING_BADGUY, WING_BADGUY2, WING_BADGUY3 };
2401 
2402 #define WING_GOODGUY_MASK ((1 << WING_BLUE_HAIR) | (1 << WING_WINGMAN))
2403 #define WING_BADGUY_MASK (0xff << WING_BADGUY)
2404 
2405 enum WingGameMode { WING_PLAY_GAME, WING_BRIEFING, WING_DEBRIEFING, WING_FLYBY, WING_YOUDIED };
2406 
create_wing_object(int type,int dam,fix x,fix y,fix z)2407 static int create_wing_object(int type, int dam, fix x, fix y, fix z) {
2408     int i;
2409     if (num_wing_objects < MAX_WING_OBJECTS) {
2410         i = num_wing_objects++;
2411         wing[i].type = type;
2412         wing[i].damage = dam;
2413         wing[i].x = x;
2414         wing[i].y = y;
2415         wing[i].z = z;
2416     } else
2417         i = -1;
2418     return i;
2419 }
2420 
wing_delete_all_but(void)2421 static void wing_delete_all_but(void) {
2422     // delete everything other than wingman
2423     int i = 1;
2424     while (i < num_wing_objects)
2425         if (wing[i].type == WING_WINGMAN)
2426             ++i;
2427         else
2428             wing[i] = wing[--num_wing_objects];
2429 }
2430 
2431 // All the things that determine units, the scale of things,
2432 // should go in this section for ease of manipulation
2433 
2434 static fix wing_velocity[] = {
2435     fix_make(2, 0),      // BLUE_HAIR
2436     fix_make(8, 0),      // SHOT
2437     fix_make(0, 0),      // BOOM
2438     fix_make(4, 0),      // WINGMAN
2439     fix_make(3, 0),      // BADGUY
2440     fix_make(2, 0x8000), // BADGUY2
2441     fix_make(5, 0)       // BADGUY3
2442 };
2443 
2444 static int wing_damage_amount[] = {20, 1,
2445                                    20, // countdown timer for explosion
2446                                    20, 8, 12, 16};
2447 
2448 #define WING_FIRING_RANGE fix_make(200, 0)
2449 #define WING_TRAIL        32 // 32 frames behind other person
2450 #define FORMATION         fix_make(48, 0)
2451 #define WING_HIT_DISTANCE fix_make(14, 0) // was 20
2452 
2453 #define SHOT_TIME       30
2454 #define SHOT_VALID_TIME 27
2455 #define WING_ANIM_CYCLE (CIT_CYCLE / 4)
2456 #define WING_CYCLE      (CIT_CYCLE / 30)
2457 
2458 #define WING_PHASES 4
2459 
2460 #define WING_NUM_MISSIONS 13
2461 #define GHANDI_LEVEL(x) ((x) >= 4 * WING_PHASES && (x) < 5 * WING_PHASES)
2462 
2463 // Wing commander "levels"
2464 
2465 #define W_BAD1 1
2466 #define W_BAD2 8
2467 #define W_BAD3 64
2468 
2469 uchar wing_level_data[] = {
2470     // ? sector
2471     W_BAD1, W_BAD1, 0, W_BAD1, W_BAD1, 0, W_BAD1, 2 * W_BAD1, W_BAD1 * 2, W_BAD1, W_BAD1, W_BAD1 * 4,
2472 
2473     // Scary Sector
2474     0, 0, W_BAD1 * 2, W_BAD1 + W_BAD2, 0, W_BAD1 * 2, 0, W_BAD1 + W_BAD2 * 2, W_BAD2 * 2, 0, W_BAD1, W_BAD1 * 3,
2475     W_BAD1 * 2, 0, W_BAD1 * 3 + W_BAD2, W_BAD1 * 2 + W_BAD2 * 2,
2476 
2477     // Spilt Milk Sector
2478     W_BAD1 * 2, 0, W_BAD1 * 2 + W_BAD2 * 2, W_BAD1 * 2, 0, W_BAD1 * 2 + W_BAD2, W_BAD2 * 3, 0, W_BAD1 + W_BAD2 * 2,
2479     W_BAD1 * 3, W_BAD1 * 4 + W_BAD2 * 2, W_BAD1 * 3 + W_BAD2 * 4 + W_BAD3 * 3,
2480 
2481     // Tiger sector
2482     0, W_BAD2 * 4, 0, W_BAD1 * 2 + W_BAD3 * 2, 0, 0, 0, W_BAD1 * 7 + W_BAD2 * 5 + W_BAD3 * 3, W_BAD1 * 4,
2483     W_BAD1 * 3 + W_BAD2 * 2, W_BAD1 * 2 + W_BAD2 * 3, W_BAD1 * 2 + W_BAD2 * 2 + W_BAD3 * 3};
2484 
2485 uchar wing_wingmen[WING_NUM_MISSIONS] = {1, 1, 1,    // ? Sector
2486                                          0, 1, 1, 1, // Scary sector
2487                                          1, 1, 2, 1, 6, 0};
2488 
2489 #define LEVEL_WINGMAN_COUNT() wing_wingmen[wing_level / WING_PHASES]
2490 
2491 enum WingMessage {
2492     WING_SILENT = 0,
2493     WING_SAYS_SIGHTED,
2494     WING_SAYS_DIE,
2495     WING_SAYS_ATTACK,
2496     WING_SAYS_FORM,
2497     WING_NO_WINGMAN
2498 };
2499 
2500 #define WING_MESSAGE_COUNT 30
2501 
wing_distance(fix a,fix b,fix c)2502 static fix wing_distance(fix a, fix b, fix c) {
2503     fix t;
2504 
2505     a = abs(a);
2506     b = abs(b);
2507     c = abs(c);
2508     t = a > b ? a + b / 2 : b + a / 2;
2509     t = c > t ? c + t / 2 : t + c / 2;
2510 
2511     return t;
2512 }
2513 
wing_play_fx(int sound,int obj,int radius)2514 static void wing_play_fx(int sound, int obj, int radius) {
2515     if (sound == SFX_NONE)
2516         return;
2517     if (obj != 0) {
2518         if (fix_int(wing_distance(wing[obj].x, wing[obj].y, wing[obj].z)) > radius)
2519             return;
2520     }
2521     play_digi_fx(sound, 1);
2522 }
2523 
wing_set_message(int mess)2524 static void wing_set_message(int mess) {
2525     wing_message = mess;
2526     wing_message_timer = ((mess == WING_SILENT) ? 0 : WING_MESSAGE_COUNT);
2527     if (mess == WING_SAYS_SIGHTED && wing_level < WING_PHASES) {
2528         wing_play_fx(WING_SFX_THEYRE_ATTACKING_US_SIR, 0, 0);
2529     }
2530 }
2531 
wing_find_wingman(int i)2532 static int wing_find_wingman(int i) {
2533     for (++i; i < num_wing_objects; ++i)
2534         if (wing[i].type == WING_WINGMAN)
2535             break;
2536     return i == num_wing_objects ? 0 : i;
2537 }
2538 
wingman_order(void)2539 static void wingman_order(void) {
2540     int i = wing_find_wingman(0);
2541     if (!i)
2542         wing_set_message(WING_NO_WINGMAN);
2543     else if (wingman_mode == WINGMAN_FORMATION) {
2544         wingman_mode = WINGMAN_ATTACK;
2545         wing_set_message(WING_SAYS_ATTACK);
2546     } else {
2547         wingman_mode = WINGMAN_FORMATION;
2548         wing_set_message(WING_SAYS_FORM);
2549     }
2550 }
2551 
wing_any_enemies(void)2552 static int wing_any_enemies(void) {
2553     int i;
2554     for (i = 1; i < num_wing_objects; ++i)
2555         if (wing[i].type >= WING_BADGUY || wing[i].type == WING_BOOM)
2556             return 1;
2557     return 0;
2558 }
2559 
2560 //
2561 // Wing Commander AIs
2562 //
2563 // These blow ping out of the water.  You'll see.
2564 //
2565 
2566 // macros to determine when the AI should act
2567 #define wing_let_ai_fire(w) (!(wing_frame_count & 7) && ((w)->type != WING_WINGMAN || !GHANDI_LEVEL(wing_level)))
2568 #define wing_change_ai_facing(w) (!(wing_frame_count & 15) && (rand() & 8))
2569 
2570 // Manhattan transfer
2571 //   (once this used manhattan distance, now it uses "octagon" distance)
2572 #define man_distance(w, a, b, c) wing_distance((w)->x - (a), (w)->y - (b), (w)->z - (c))
2573 #define man_next_to(w, q, a, b, c) man_distance(w, (q)->x + (a), (q)->y + (b), (q)->z + (c))
2574 #define man_guy(w, q) man_distance(w, (q)->x, (q)->y, (q)->z)
2575 
2576 // More interesting movement
2577 #define random_vel_adjust() (fix_make(0, (rand() % 512 - 256) << 6))
2578 
wing_find_nearest(wing_obj * w,int mask)2579 static wing_obj *wing_find_nearest(wing_obj *w, int mask) {
2580     int i;
2581     fix d, e;
2582     wing_obj *z = 0;
2583 
2584     d = 0x7fffffff;
2585 
2586     for (i = 0; i < num_wing_objects; ++i) {
2587         if ((1u << wing[i].type) & mask) {
2588             if (wing[i].type == WING_WINGMAN && GHANDI_LEVEL(wing_level))
2589                 continue;
2590             e = man_guy(w, &wing[i]) + (rand() % fix_make(4, 0));
2591             if (e < d) {
2592                 z = &wing[i];
2593                 d = e;
2594             }
2595         }
2596     }
2597     return z;
2598 }
2599 
2600 // routines for steering
2601 
2602 // we call this with a _valid_ x,y,z velocity for w, that is one
2603 // that's not too fast.  This routine then deals with rotation
2604 // issues.
wing_try_for_velocity(wing_obj * w,fix x,fix y,fix z)2605 static void wing_try_for_velocity(wing_obj *w, fix x, fix y, fix z) {
2606     // Basically, we only let one of x,y,z change signs at a time.
2607     // We prioritize z, then x, then y, to cause things to go left/right
2608     // more then up/down
2609 
2610     if ((z >= 0 && w->dz < 0) || (z <= 0 && w->dz > 0)) {
2611         if (abs(w->dz) > FIX_UNIT)
2612             z = 0;
2613         w->dz = z;
2614     } else if ((x >= 0 && w->dx < 0) || (x <= 0 && w->x > 0)) {
2615         if (abs(w->dx) > FIX_UNIT)
2616             x = 0;
2617         w->dx = x;
2618         w->dz = z;
2619         if ((y >= 0 && w->dy < 0) || (y <= 0 && w->dy > 0)) {
2620             w->dy = 0;
2621         } else {
2622             if (abs(w->dy) > FIX_UNIT)
2623                 y = 0;
2624             w->dy = y;
2625         }
2626     } else {
2627         if (abs(w->dy) > FIX_UNIT)
2628             y = 0;
2629         w->dx = x;
2630         w->dy = y;
2631         w->dz = z;
2632     }
2633 
2634     w->dx += random_vel_adjust();
2635     w->dy += random_vel_adjust();
2636     w->dz += random_vel_adjust();
2637 }
2638 
2639 // convert (dx,dy,dz) to be of length (m)
2640 // Someone tell me why I made this fast and approximate
2641 // (note approximate square root and use of shifts instead
2642 // of divides and multiplies) when it's an MFD game?
wing_scale_velocity(fix * dx,fix * dy,fix * dz,fix m)2643 static void wing_scale_velocity(fix *dx, fix *dy, fix *dz, fix m) {
2644     fix x = *dx, y = *dy, z = *dz;
2645     fix v;
2646 
2647     // compute approximate velocity
2648     v = wing_distance(x, y, z);
2649     if (v == 0) {
2650         *dx = x;
2651         *dy = y;
2652         *dz = z;
2653         return;
2654     }
2655 
2656     // scale to guy's maximum velocity
2657     while (v < m / 2)
2658         v *= 2, x *= 2, y *= 2, z *= 2;
2659     while (v >= m)
2660         v /= 2, x /= 2, y /= 2, z /= 2;
2661 
2662     if (v < m - m / 4)
2663         v = v + v / 2, x = x + x / 2, y = y + y / 2, z = z + z / 2;
2664 
2665     *dx = x;
2666     *dy = y;
2667     *dz = z;
2668 }
2669 
wing_try_to_goto(wing_obj * w,fix x,fix y,fix z)2670 static void wing_try_to_goto(wing_obj *w, fix x, fix y, fix z) {
2671     // compute effective direction
2672     x -= w->x;
2673     y -= w->y;
2674     z -= w->z;
2675 
2676     wing_scale_velocity(&x, &y, &z, wing_velocity[w->type]);
2677     wing_try_for_velocity(w, x, y, z);
2678 }
2679 
2680 static fix wing_vel, wing_acc;
2681 static fixang wing_a, wing_b, wing_c;
2682 
wing_fire_shot(wing_obj * w,int side)2683 static void wing_fire_shot(wing_obj *w, int side) {
2684     int i;
2685     fix x, y, z;
2686     // fire out the front of this ship at shot velocity
2687 
2688     if (w->type == WING_BLUE_HAIR)
2689         i = create_wing_object(WING_SHOT, SHOT_TIME, w->x + w->dx, w->y + wing_vel, w->z + w->dz);
2690     else
2691         i = create_wing_object(WING_SHOT, SHOT_TIME, w->x + w->dx, w->y + w->dy, w->z + w->dz);
2692 
2693     if (i == -1)
2694         return;
2695 
2696     if (w->type == WING_BLUE_HAIR) {
2697         x = z = 0;
2698         y = wing_velocity[WING_SHOT];
2699     } else {
2700         x = w->dx;
2701         y = w->dy;
2702         z = w->dz;
2703         wing_scale_velocity(&x, &y, &z, wing_velocity[WING_SHOT]);
2704         if (x == 0 && y == 0 && z == 0) {
2705             --num_wing_objects;
2706             return;
2707         }
2708     }
2709 
2710     wing[i].x += (wing[i].dx = x) + y * side / 2;
2711     wing[i].y += (wing[i].dy = y) + x * side / 2;
2712     wing[i].z += (wing[i].dz = z) - FIX_UNIT * 4;
2713 }
2714 
wing_in_front_of(wing_obj * target,wing_obj * base)2715 static int wing_in_front_of(wing_obj *target, wing_obj *base) {
2716     // if target is in front of base, then line from base to target
2717     // is in same direction as velocity of base
2718 
2719     int x, y, z;
2720     x = fix_int(target->x - base->x);
2721     y = fix_int(target->y - base->y);
2722     z = fix_int(target->z - base->z);
2723 
2724     x = x * fix_int(base->dx * 64);
2725     y = y * fix_int(base->dy * 64);
2726     z = z * fix_int(base->dz * 64);
2727 
2728     return (x + y + z > 0);
2729 }
2730 
wing_ai_fire(wing_obj * w,wing_obj * z)2731 static void wing_ai_fire(wing_obj *w, wing_obj *z) {
2732     if (wing_in_front_of(z, w) && !(rand() % 4)) {
2733         wing_play_fx(w->type == WING_WINGMAN ? WING_SFX_GOODGUY_FIRE : WING_SFX_BADGUY_FIRE, w - wing, WING_HEAR_FIRE);
2734         wing_fire_shot(w, -1);
2735         wing_fire_shot(w, 1);
2736     }
2737 }
2738 
wing_do_ai(wing_obj * w)2739 static void wing_do_ai(wing_obj *w) {
2740     int mask;
2741     wing_obj *z;
2742 
2743     switch (w->type) {
2744     case WING_BLUE_HAIR:
2745         return;
2746 
2747     case WING_SHOT:
2748     case WING_BOOM:
2749         --w->damage; // countdown until it is dead
2750         break;
2751 
2752     case WING_WINGMAN:
2753         switch (wingman_mode) {
2754         case WINGMAN_FORMATION:
2755             // Formation:
2756             //   If near enough to player, turn to face same direction
2757             //   else turn towards player's destination
2758 
2759             if (wing_change_ai_facing(w)) {
2760                 if (man_next_to(w, &wing[0], -FORMATION / 2, 0, 0) < FORMATION * 2) {
2761                     if (wing_vel > wing_velocity[w->type])
2762                         wing_try_for_velocity(w, 0, wing_velocity[w->type], 0);
2763                     else
2764                         wing_try_for_velocity(w, 0, wing_vel, 0);
2765                 } else {
2766                     // We should try to lead player somewhat, but
2767                     // it depends how close we are.  Hmm.
2768                     // We'll just go to where he was, and curve in.
2769                     wing_try_to_goto(w, -FORMATION / 2, 0, 0);
2770                 }
2771             }
2772             if (wing_let_ai_fire(w)) {
2773                 z = wing_find_nearest(w, WING_BADGUY_MASK);
2774                 if (z && man_guy(w, z) < WING_FIRING_RANGE)
2775                     wing_ai_fire(w, z);
2776             }
2777             break;
2778 
2779         case WINGMAN_ATTACK:
2780             mask = WING_BADGUY_MASK;
2781             goto attack_ai;
2782         }
2783         break;
2784 
2785     default:
2786         mask = WING_GOODGUY_MASK;
2787         // fallthrough
2788 
2789     attack_ai:
2790         z = wing_find_nearest(w, mask);
2791         if (z == 0 && w->type == WING_WINGMAN) {
2792             wingman_mode = WINGMAN_FORMATION;
2793             if (!(rand() % 4))
2794                 wing_set_message(WING_SAYS_FORM);
2795         }
2796         if (z && wing_change_ai_facing(w)) {
2797             // if we're not in firing range, just move towards
2798             // otherwise if we're behind him turn to face him
2799             if (man_guy(w, z) < WING_FIRING_RANGE)
2800                 //  seems better not to do this    || !wing_in_front_of(w,z))
2801                 wing_try_to_goto(w, z->x, z->y, z->z);
2802             else
2803                 // otherwise try to get behind him
2804                 wing_try_to_goto(w, z->x - WING_TRAIL * (z->dx + z->dy / 2), z->y - WING_TRAIL * (z->dy - z->dx / 2),
2805                                  z->z - WING_TRAIL * z->dz);
2806         }
2807         if (z && man_guy(w, z) < WING_FIRING_RANGE && wing_let_ai_fire(w)) {
2808             wing_ai_fire(w, z);
2809         }
2810         // end of cases of object types
2811     }
2812 }
2813 
wing_rotate_vector(fix * v,fix sina,fix cosa,fix sinb,fix cosb,fix sinc,fix cosc)2814 static void wing_rotate_vector(fix *v, fix sina, fix cosa, fix sinb, fix cosb, fix sinc, fix cosc) {
2815     fix x, y, z;
2816 
2817     y = fix_mul(cosa, v[1]) + fix_mul(sina, v[2]);
2818     z = fix_mul(cosa, v[2]) - fix_mul(sina, v[1]);
2819 
2820     v[1] = fix_mul(cosb, y) - fix_mul(sinb, v[0]);
2821     x = fix_mul(cosb, v[0]) + fix_mul(sinb, y);
2822 
2823     v[2] = fix_mul(cosc, z) - fix_mul(sinc, x);
2824     v[0] = fix_mul(cosc, x) + fix_mul(sinc, z);
2825 }
2826 
wing_move_world(fixang a,fixang b,fixang c,fix v)2827 static void wing_move_world(fixang a, fixang b, fixang c, fix v) {
2828     // move the world because player rotated by a & b
2829     // and moved forward by velocity v
2830 
2831     int i;
2832     fix sina, cosa, sinb, cosb, sinc, cosc;
2833     fix_sincos(a, &sina, &cosa);
2834     fix_sincos(b, &sinb, &cosb);
2835     fix_sincos(c, &sinc, &cosc);
2836     for (i = 1; i < num_wing_objects; ++i) {
2837 
2838         if (wing_game_mode == WING_PLAY_GAME)
2839             wing_do_ai(&wing[i]);
2840 
2841         wing_rotate_vector(&wing[i].x, sina, cosa, sinb, cosb, sinc, cosc);
2842         wing_rotate_vector(&wing[i].dx, sina, cosa, sinb, cosb, sinc, cosc);
2843 
2844         wing[i].x += wing[i].dx;
2845         wing[i].y += wing[i].dy - v; // adjust for player's velocity
2846         wing[i].z += wing[i].dz;
2847     }
2848     for (i = 0; i < MAX_WING_STARS; ++i)
2849         wing_rotate_vector(&wing_st[i].x, sina, cosa, sinb, cosb, sinc, cosc);
2850 }
2851 
wing_handle_collisions(void)2852 static void wing_handle_collisions(void) {
2853     int i, j;
2854     // delete any objects which are dead
2855     i = 1;
2856     while (i < num_wing_objects)
2857         if (wing[i].damage <= 0) {
2858             if (wing[i].type == WING_WINGMAN)
2859                 wing_set_message(WING_SAYS_DIE);
2860             if (wing[i].type != WING_SHOT && wing[i].type != WING_BOOM) {
2861                 // turn guys into explosions
2862                 wing[i].type = WING_BOOM;
2863                 wing[i].damage = wing_damage_amount[wing[i].type];
2864                 ++i;
2865                 wing_play_fx(WING_SFX_EXPLODE, i, WING_HEAR_EXPLODE);
2866             } else
2867                 wing[i] = wing[--num_wing_objects];
2868         } else
2869             ++i;
2870 
2871     for (j = 1; j < num_wing_objects; ++j) {
2872         // check object j against all objects
2873         // we only check for collisions of shots against ships
2874         if (wing[j].type == WING_SHOT && wing[j].damage < SHOT_VALID_TIME) {
2875             for (i = 0; i < num_wing_objects; ++i) {
2876                 if (wing[i].type != WING_SHOT && wing[i].type != WING_BOOM) {
2877                     // are they in range of each other?
2878                     if (man_guy(&wing[i], &wing[j]) < WING_HIT_DISTANCE) {
2879                         // do damage to both; shots always die
2880                         wing[i].damage -= wing_damage_amount[wing[j].type];
2881                         wing[j].damage = 0;
2882                         wing_play_fx((i == 0) ? WING_SFX_HIT_PLAYER : WING_SFX_HIT_OTHER, i, WING_HEAR_HIT);
2883                     }
2884                 }
2885             }
2886         }
2887     }
2888 }
2889 
wing_update_one_time_unit(void)2890 static void wing_update_one_time_unit(void) {
2891     for (; games_time_diff >= WING_CYCLE; games_time_diff -= WING_CYCLE) {
2892         wing_vel += wing_acc;
2893         if (wing_vel < 0)
2894             wing_vel = 0;
2895         if (wing_vel > wing_velocity[0])
2896             wing_vel = wing_velocity[0];
2897 
2898         ++wing_frame_count;
2899 
2900         wing_move_world(wing_a, wing_b, wing_c, wing_vel);
2901         wing_handle_collisions();
2902     }
2903     if (wing[0].damage <= 0)
2904         wing_game_mode = WING_YOUDIED;
2905 }
2906 
2907 #define WING_FACE_FORWARD 0
2908 #define WING_FACE_LEFT    1
2909 #define WING_FACE_AWAY    2
2910 #define WING_FACE_RIGHT   3
2911 #define WING_FACE_UP      4
2912 #define WING_FACE_DOWN    5
2913 
wing_get_facing(int i)2914 static int wing_get_facing(int i) {
2915     int dx = abs(wing[i].dx);
2916     int dy = abs(wing[i].dy);
2917     int dz = abs(wing[i].dz);
2918 
2919     if (dx > dy)
2920         if (dx > dz)
2921             return wing[i].dx > 0 ? WING_FACE_RIGHT : WING_FACE_LEFT;
2922         else
2923             return wing[i].dz > 0 ? WING_FACE_UP : WING_FACE_DOWN;
2924     else if (dy > dz)
2925         return wing[i].dy > 0 ? WING_FACE_AWAY : WING_FACE_FORWARD;
2926     else
2927         return wing[i].dz > 0 ? WING_FACE_UP : WING_FACE_DOWN;
2928 }
2929 
scale_res_bm(int ref,int x,int y,int w,int h)2930 static void scale_res_bm(int ref, int x, int y, int w, int h) {
2931     FrameDesc *f;
2932 
2933     f = RefLock(ref);
2934     if (!f)
2935         return;
2936 
2937     f->bm.bits = (uchar *)(f + 1);
2938     ss_scale_bitmap(&f->bm, x, y, w, h);
2939 
2940     RefUnlock(ref);
2941 }
2942 
2943 #define BRIEF_X 2
2944 #define BRIEF_Y (MFD_VIEW_HGT - 6)
2945 
2946 #define WING_NUMCHARS 26
2947 
2948 // the cleverer flow-text-up-from-the-bottom-
2949 // so-that-it-always-fits routine
2950 
wing_print_message(char * s,int y)2951 static void wing_print_message(char *s, int y) {
2952     char *t, *u, c;
2953     int x;
2954 
2955     u = s + strlen(s) - 1;
2956     while (u - s + 1 > WING_NUMCHARS / 2) {
2957         // u points to the last character which hasn't been plotted
2958         // find last whitespace WING_NUMCHARS or fewer characters
2959         if (u - WING_NUMCHARS < s)
2960             t = s - 1;
2961         else {
2962             t = u - WING_NUMCHARS;
2963             while (*t != ' ') {
2964                 if (!*t)
2965                     goto ouch;
2966                 ++t;
2967             }
2968         }
2969         // print that much of s
2970         c = u[1];
2971         u[1] = 0;
2972         // wait, first let's check if that's too long
2973         while (gr_string_width(t + 1) > MFD_VIEW_WID - BRIEF_X - 1) {
2974             ++t;
2975             while (*t != ' ') {
2976                 if (!*t)
2977                     goto ouch;
2978                 ++t;
2979             }
2980         }
2981         if (0) {
2982         ouch:
2983             if (u - WING_NUMCHARS < s)
2984                 t = s - 1;
2985             else
2986                 t = u - WING_NUMCHARS;
2987         }
2988         ss_string(t + 1, BRIEF_X, y);
2989         u[1] = c;
2990         u = t - 1;
2991         y -= 5;
2992     }
2993     if (u >= s) {
2994         c = u[1];
2995         u[1] = 0;
2996         if (u - s < WING_NUMCHARS / 2)
2997             x = (MFD_VIEW_WID - gr_string_width(s)) / 2 - BRIEF_X;
2998         else
2999             x = 0;
3000         ss_string(s, BRIEF_X + x, y);
3001         u[1] = c;
3002     }
3003 }
3004 
3005 #define WING_VIEW_ANGLE_SCALE_THING fix_make(32, 0)
3006 
3007 static int wing_radius[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 18, 21, 25, 28, 26, 22, 16, 1000};
3008 
wing_render_world(void)3009 static void wing_render_world(void) {
3010     int i, sx, sy, k, j;
3011     fix sc, q;
3012     wing_obj temp;
3013 
3014     gr_set_fcolor(BLACK + 1);
3015     ss_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
3016 
3017     for (i = 0; i < MAX_WING_STARS; ++i) {
3018         if (wing_st[i].y >= fix_make(0, 0x1000)) {
3019             sc = fix_div(WING_VIEW_ANGLE_SCALE_THING, wing_st[i].y);
3020             sx = fix_int(fix_mul(sc, wing_st[i].x)) + MFD_VIEW_MID;
3021             sy = MFD_VIEW_HGT / 2 - fix_int(fix_mul(sc, wing_st[i].z));
3022             ss_set_pixel(WHITE, sx, sy);
3023         }
3024     }
3025 
3026     // depth sort the objects with an insertion sort
3027     for (i = 2; i < num_wing_objects; ++i) {
3028         temp = wing[i];
3029         q = temp.y;
3030         j = i - 1;
3031         while (j > 0) {
3032             if (q < wing[j].y)
3033                 break;
3034             wing[j + 1] = wing[j];
3035             --j;
3036         }
3037         wing[j + 1] = temp;
3038     }
3039 
3040     for (i = 1; i < num_wing_objects; ++i) {
3041         if (wing[i].y >= fix_make(0, 0x1000)) {
3042             sc = fix_div(WING_VIEW_ANGLE_SCALE_THING, wing[i].y);
3043             sx = fix_int(fix_mul(sc, wing[i].x)) + MFD_VIEW_MID;
3044             sy = MFD_VIEW_HGT / 2 - fix_int(fix_mul(sc, wing[i].z));
3045             switch (wing[i].type) {
3046             case WING_WINGMAN:
3047             case WING_BLUE_HAIR:
3048                 k = fix_int(sc * 16) + 1;
3049                 scale_res_bm(REF_IMG_wing_ship + wing_get_facing(i), sx - k / 2, sy - k / 2, k, k);
3050                 break;
3051             case WING_SHOT:
3052                 k = fix_int(sc * 1) + 1;
3053                 if (k > 1)
3054                     scale_res_bm(REF_IMG_DepthCharge, sx, sy, fix_int(sc * 1) + 1, fix_int(sc * 1) + 1);
3055                 else
3056                     ss_set_pixel(ORANGE_8_BASE, sx, sy);
3057                 break;
3058             case WING_BOOM:
3059                 k = fix_int(sc * wing_radius[wing[i].damage]) + 1;
3060                 gr_set_fcolor(RED_8_BASE + 2 + rand() % 4);
3061                 ss_int_disk(sx, sy, k);
3062                 break;
3063 
3064             default:
3065                 k = fix_int(sc * 16) + 1;
3066                 scale_res_bm(REF_IMG_wing_bad1 + 6 * (wing[i].type - WING_BADGUY) + wing_get_facing(i), sx - k / 2,
3067                              sy - k / 2, k, k);
3068             }
3069         }
3070     }
3071 
3072     for (i = 1; i < num_wing_objects; ++i)
3073         if (wing[i].type > WING_BOOM) {
3074             sx = fix_rint(wing[i].x / 128) + 10;
3075             sy = fix_rint(wing[i].y / 128) + 10;
3076             if (sx < 0)
3077                 sx = 0;
3078             else if (sx > 20)
3079                 sx = 20;
3080             if (sy < 0)
3081                 sy = 0;
3082             else if (sy > 20)
3083                 sy = 20;
3084             ss_set_pixel((wing[i].type == WING_WINGMAN ? GREEN_8_BASE : RED_8_BASE) + 2, sx, MFD_VIEW_HGT - 1 - sy);
3085         }
3086     ss_set_pixel(GREEN_8_BASE + 3, 10, MFD_VIEW_HGT - 1 - 10);
3087 
3088     {
3089         char buffer[16];
3090         sprintf(buffer, "%03d", wing_vel * 150 / wing_velocity[0]);
3091         gr_set_fcolor(WHITE);
3092         ss_string(buffer, MFD_VIEW_WID - 16, 0);
3093     }
3094 
3095     if (wing_message) {
3096         char *s;
3097         int z = wing_level / WING_PHASES;
3098         switch (wing_message) {
3099         case WING_SAYS_SIGHTED:
3100             s = STRING(WingSighted + z);
3101             break;
3102         case WING_SAYS_DIE:
3103             s = STRING(WingDies + z);
3104             break;
3105         case WING_SAYS_ATTACK:
3106             s = STRING(WingAttack + z);
3107             break;
3108         case WING_SAYS_FORM:
3109             s = STRING(WingForm + z);
3110             break;
3111         case WING_NO_WINGMAN:
3112             s = STRING(NoWing);
3113             break;
3114         }
3115         gr_set_fcolor(GREEN_8_BASE);
3116         wing_print_message(s, MFD_VIEW_HGT - 6);
3117 
3118         if (!--wing_message_timer)
3119             wing_message = 0;
3120     }
3121 }
3122 
3123 // this should actually take as a parameter
wing_play_cutscene(char * s)3124 static void wing_play_cutscene(char *s) {
3125     int anim;
3126     if (!full_game_3d)
3127         draw_res_bm(REF_IMG_bmBlankMFD, 0, 0);
3128     anim = (player_struct.game_time / WING_ANIM_CYCLE) % 3;
3129     draw_res_bm(REF_IMG_GoofyNed + anim, 20, 2);
3130 
3131     // now display the text
3132     gr_set_fcolor(GRAY_8_BASE + 1);
3133     wing_print_message(s, BRIEF_Y);
3134 }
3135 
wing_start_minor_level(void)3136 static void wing_start_minor_level(void) {
3137     int i, j, wingman = 0, t, n;
3138     wing_game_mode = WING_PLAY_GAME;
3139 
3140     // reset player speed
3141     wing[0].dx = wing[0].dz = 0;
3142     wing[0].dy = wing_vel = wing_velocity[WING_BLUE_HAIR] / 2;
3143 
3144     wing_a = wing_b = wing_c = 0;
3145     wing_acc = 0;
3146 
3147     // delete everything other than wingman
3148     wing_delete_all_but();
3149 
3150     // rotate the stars
3151     wing_move_world(0, 0x8000, 0, 0);
3152 
3153     i = wing_find_wingman(0);
3154     n = 0;
3155     while (i) {
3156         wing[i].dx = wing[i].dz = 0;
3157         wing[i].dy = wing[0].dy;
3158         wing[i].y = wing[i].z = 0;
3159         wing[i].x = -FORMATION / 3 + n++ * FORMATION / 2;
3160         wingman = 1;
3161         i = wing_find_wingman(i);
3162     }
3163 
3164     // create the enemy
3165     n = wing_level_data[wing_level];
3166     if (n && wingman)
3167         wing_set_message(WING_SAYS_SIGHTED);
3168     else
3169         wing_set_message(WING_SILENT);
3170 
3171     t = WING_BADGUY;
3172     while (n) {
3173         for (i = 0; i < (n & 7); ++i) {
3174             j = create_wing_object(t, wing_damage_amount[t], fix_make(rand() % 512 - 256, 0),
3175                                    fix_make(rand() % 64 + 768, 0), fix_make(rand() % 512 - 256, 0));
3176             if (j != -1) {
3177                 wing[j].dx = (rand() % 512 - 256) * 256;
3178                 wing[j].dy = (rand() % 512 - 256) * 256;
3179                 wing[j].dz = (rand() % 512 - 256) * 256;
3180             }
3181         }
3182         n >>= 3u;
3183         ++t;
3184     }
3185 
3186     wingman_mode = WINGMAN_FORMATION;
3187     games_time_diff = 0;
3188 }
3189 
wing_start_major_level(void)3190 static void wing_start_major_level(void) {
3191     int j;
3192 
3193     // reset all objects so player has new damage amount
3194     // and so there's always a wingman if there should be
3195     num_wing_objects = 0;
3196     // allocate player object
3197     j = create_wing_object(WING_BLUE_HAIR, wing_damage_amount[WING_BLUE_HAIR], 0, 0, 0);
3198     wing_vel = wing_velocity[WING_BLUE_HAIR] / 2;
3199     wing_acc = 0;
3200 
3201     wing[j].dx = wing[j].dz = 0;
3202     wing[j].dy = wing_vel; // so badguys can try to get behind you
3203 
3204     for (j = 0; j < LEVEL_WINGMAN_COUNT(); ++j)
3205         create_wing_object(WING_WINGMAN, wing_damage_amount[WING_WINGMAN], -FORMATION / 2 - FORMATION, 0, 0);
3206 
3207     wing_start_minor_level();
3208 }
3209 
wing_start_flyby(void)3210 static void wing_start_flyby(void) {
3211     int i;
3212 
3213     wing_play_fx(WING_SFX_AUTOPILOT, 0, 0);
3214     wing_delete_all_but();
3215     wing_move_world(0, 0x8000, 0, 0);
3216     // create a dummy object for the player
3217 
3218     i = create_wing_object(WING_BLUE_HAIR, 1, 0, 0, 0);
3219 
3220     for (i = 1; i < num_wing_objects; ++i) {
3221         wing[i].z = fix_make(6, 0) * (i - 1);
3222         wing[i].y = fix_make(140, 0) - fix_make(8, 0) * i;
3223         wing[i].x = fix_make(1, 0) + fix_make(35, 0) * i;
3224         wing[i].dx = wing[i].dz = 0;
3225         wing[i].dy = -fix_make(4, 0);
3226     }
3227 
3228     wing_frame_count = 0;
3229     wing_game_mode = WING_FLYBY;
3230     games_time_diff = 0;
3231 }
3232 
wing_handle_flyby(void)3233 static void wing_handle_flyby(void) {
3234     wing_a = wing_c = 0;
3235     wing_b = -0x1c0;
3236     wing_vel = 0;
3237     wing_update_one_time_unit();
3238     wing_render_world();
3239 
3240     if (wing_frame_count > 64) {
3241         wing_start_minor_level();
3242     }
3243 }
3244 
wing_advance_to_next_level(void)3245 static void wing_advance_to_next_level(void) {
3246     if (wing_level % WING_PHASES == WING_PHASES - 1) {
3247         wing_game_mode = WING_DEBRIEFING;
3248         QUESTVAR_SET(WING_QUEST_VAR, wing_level + 1);
3249     } else {
3250         ++wing_level;
3251         wing_start_flyby();
3252     }
3253 }
3254 
games_handle_wing(MFD * m,uiEvent * e)3255 uchar games_handle_wing(MFD *m, uiEvent *e) {
3256     LGPoint pos = MakePoint(e->pos.x - m->rect.ul.x, e->pos.y - m->rect.ul.y);
3257     fix x, y;
3258 
3259     if (wing_game_mode != WING_PLAY_GAME) {
3260         if (!(e->mouse_data.action & MOUSE_LDOWN))
3261             return FALSE;
3262         if (wing_game_mode == WING_DEBRIEFING) {
3263             wing_game_mode = WING_BRIEFING;
3264             ++wing_level;
3265             return TRUE;
3266         }
3267         if (wing_game_mode == WING_BRIEFING) {
3268             if (wing_level == WING_NUM_MISSIONS * WING_PHASES) {
3269                 QUESTVAR_SET(WING_QUEST_VAR, 0);
3270                 GAME_MODE = GAME_MODE_MENU;
3271                 return TRUE;
3272             }
3273             wing_start_major_level();
3274             return TRUE;
3275         }
3276         if (wing_game_mode == WING_YOUDIED) {
3277             wing_level = QUESTVAR_GET(WING_QUEST_VAR);
3278             wing_game_mode = WING_BRIEFING;
3279             return TRUE;
3280         }
3281         return FALSE;
3282     }
3283 
3284     x = (pos.x * FIX_UNIT * 2) / MFD_VIEW_WID - FIX_UNIT;
3285     y = (pos.y * FIX_UNIT * 2) / MFD_VIEW_HGT - FIX_UNIT;
3286 
3287     if (abs(x) < fix_make(0, 4096))
3288         x = 0;
3289     else
3290         x = x + (x > 0 ? -4096 : 4096);
3291 
3292     if (abs(y) < fix_make(0, 4096))
3293         y = 0;
3294     else
3295         y = y + (y > 0 ? -4096 : 4096);
3296 
3297 #ifdef PLAYTEST
3298     if (wing_cheat && (e->mouse_data.action & MOUSE_RDOWN) && (e->mouse_data.buttons & 3) == 3) {
3299         // right click while left button held
3300         wing_delete_all_but();
3301         wing_level |= 3;
3302         return TRUE;
3303     }
3304 #endif
3305 
3306     if (e->mouse_data.action & MOUSE_LDOWN) {
3307         if ((e->mouse_data.buttons & 3) == 3) {
3308             // both buttons, assume it's an order
3309             wingman_order();
3310             wing_play_fx(WING_SFX_COMM, 0, 0);
3311             return TRUE;
3312         }
3313         if (!wing_any_enemies()) {
3314             wing_advance_to_next_level();
3315             return TRUE;
3316         }
3317         wing_play_fx(WING_SFX_GOODGUY_FIRE, 0, 0);
3318         wing_fire_shot(wing, -1);
3319         wing_fire_shot(wing, 1);
3320         return TRUE;
3321     }
3322 
3323     switch (e->mouse_data.buttons & 3) {
3324     case 0:
3325     case 1:
3326     case 3: // both buttons pushed.  Huh.
3327         wing_a = -y / 64;
3328         wing_b = -x / 64;
3329         wing_c = 0;
3330         wing_acc = 0;
3331         break;
3332 
3333     case 2: // do right mouse stuff
3334         wing_acc = -y / 4;
3335         wing_c = x / 64;
3336         wing_a = wing_b = 0;
3337     }
3338 
3339     return TRUE;
3340 }
3341 
games_init_wing(void * game_state)3342 void games_init_wing(void *game_state) {
3343     int i;
3344     wing_level = QUESTVAR_GET(WING_QUEST_VAR);
3345     wing_game_mode = WING_BRIEFING;
3346 
3347     for (i = 0; i < MAX_WING_STARS; ++i) {
3348         wing_st[i].x = fix_make(rand() % 512 - 256, 0);
3349         wing_st[i].y = fix_make(rand() % 512 - 256, 0);
3350         wing_st[i].z = fix_make(rand() % 512 - 256, 0);
3351     }
3352 }
3353 
games_expose_wing(MFD * m,ubyte control)3354 void games_expose_wing(MFD *m, ubyte control) {
3355     switch (wing_game_mode) {
3356     case WING_PLAY_GAME:
3357         wing_update_one_time_unit();
3358         wing_render_world();
3359         break;
3360 
3361     case WING_BRIEFING:
3362         wing_play_cutscene(STRING(WingBriefing + wing_level / WING_PHASES));
3363         break;
3364 
3365     case WING_DEBRIEFING:
3366         wing_play_cutscene(STRING(WingDebriefing + wing_level / WING_PHASES));
3367         break;
3368 
3369     case WING_YOUDIED:
3370         wing_play_cutscene(STRING(WingYouDied));
3371         break;
3372 
3373     case WING_FLYBY:
3374         wing_handle_flyby();
3375         break;
3376     }
3377 
3378     mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
3379     // autoreexpose
3380     mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
3381 }
3382 
3383 #endif // LOST_TREASURES
3384 
3385 /*
3386 // this is so lovely, it is a test function, joy
3387 uchar mfd_games_hack_func(short keycode, ulong context, void* data)
3388 {
3389    mcom_state *cur_state=(mcom_state *)GAME_DATA;
3390    int mfd = mfd_grab_func(MFD_GAMES_FUNC,MFD_INFO_SLOT);
3391 
3392         // give the tester all of the frigging games
3393    player_struct.softs.misc[MISC_SOFTWARE_GAMES] = 0xff;
3394    GAME_MODE = GAME_MODE_MENU;
3395    mfd_notify_func(MFD_GAMES_FUNC,MFD_INFO_SLOT,TRUE,MFD_ACTIVE,TRUE);
3396    mfd_change_slot(mfd,MFD_INFO_SLOT);
3397 #ifdef PLAYTEST
3398    wing_cheat = 1;
3399 #endif
3400    return FALSE;
3401 }
3402 */
3403 
mfd_games_turnon(bool b,uchar real_start)3404 void mfd_games_turnon(bool b, uchar real_start) {
3405     if (real_start) {
3406         int mfd = mfd_grab_func(MFD_GAMES_FUNC, MFD_INFO_SLOT);
3407         GAME_MODE = GAME_MODE_MENU;
3408         mfd_notify_func(MFD_GAMES_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);
3409         mfd_change_slot(mfd, MFD_INFO_SLOT);
3410         player_struct.softs_status.misc[CPTRIP(GAMES_TRIPLE)] &= ~WARE_ON;
3411     }
3412 }
3413 
mfd_games_turnoff(bool b,uchar real_start)3414 void mfd_games_turnoff(bool b, uchar real_start) {
3415     // game shutdown code goes here.
3416 }
3417 
3418 uchar (*game_handler_funcs[])(MFD *m, uiEvent *ev) = {
3419     games_handle_pong,
3420     games_handle_mcom,
3421     games_handle_road,
3422     games_handle_pong, // just reuse the pong code for bots
3423     games_handle_15,
3424     games_handle_ttt,
3425     games_handle_null,
3426     games_handle_wing,
3427     games_handle_menu
3428 };
3429