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