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/gamewrap.c $
21 * $Revision: 1.101 $
22 * $Author: xemu $
23 * $Date: 1994/11/26 03:36:36 $
24 */
25
26 #define __GAMEWRAP_SRC
27
28 #include <string.h>
29
30 #include "Shock.h"
31 #include "ShockDialogs.h"
32
33 #include "amap.h"
34 #include "archiveformat.h"
35 #include "criterr.h"
36 #include "cybmem.h"
37 #include "cybstrng.h"
38 #include "dynmem.h"
39 #include "faketime.h"
40 #include "gamewrap.h"
41 #include "hkeyfunc.h"
42 #include "invent.h"
43 #include "invpages.h"
44 #include "mainloop.h"
45 #include "map.h"
46 #include "miscqvar.h"
47 #include "musicai.h" // to tweak music upon startup
48 #include "newmfd.h"
49 #include "objects.h"
50 #include "objload.h"
51 #include "objsim.h"
52 #include "olhext.h"
53 #include "player.h"
54 #include "saveload.h"
55 #include "schedule.h"
56 #include "shodan.h"
57 #include "sideicon.h"
58 #include "status.h"
59 #include "tools.h"
60 #include "wares.h"
61
62 #include "otrip.h"
63
64 #include <stdio.h>
65
66 /*
67 #include <physics.h>
68 #include <tilemap.h>
69 #include <damage.h> // for destroyed_object stuff
70 #include <gamesys.h>
71 #include <frprotox.h>
72 #include <gamestrn.h>
73 #include <gr2ss.h>
74
75 #include <objprop.h>
76 #include <objbit.h>
77 #include <hud.h>
78
79 #include <wsample.h>
80 */
81 #define SCHEDULE_BASE_ID 590
82
83 extern long old_ticks;
84 extern char saveload_string[30];
85 extern uchar display_saveload_checkpoints;
86 extern ulong obj_check_time;
87 extern uchar mlimbs_on;
88
89 // Player struct support for savegames.
90 // DOS version savegame reserves 32 bytes for puzzle state.
91 #define PL_MFD_PUZZLE_SIZE 32
92 #include "playerlayout.h"
93 #undef PL_MFD_PUZZLE_SIZE
94 // Enhanced edition uses 64.
95 #define PL_MFD_PUZZLE_SIZE 64
96 #include "playerlayout.h"
97 #undef PL_MFD_PUZZLE_SIZE
98
99 const ResLayout *PlayerLayouts[] = { &PlayerLayout_M32, &PlayerLayout_M64 };
100 // Decode wrapper for a player layout. Tries to figure out which version saved
101 // the game from the resource size.
decode_player(void * raw,size_t * size,UserDecodeData layout)102 void *decode_player(void *raw, size_t *size, UserDecodeData layout) {
103 int i;
104 for (i = 0; i < sizeof PlayerLayouts / sizeof *PlayerLayouts; ++i) {
105 if (*size == PlayerLayouts[i]->dsize) {
106 return ResDecode(raw, size, (UserDecodeData)PlayerLayouts[i]);
107 }
108 }
109 ERROR("Could not determine format of saved player!");
110 return NULL;
111 }
112 // Player format. We always save as enhanced format (64-byte MFD array).
113 const ResourceFormat PlayerFormat = {
114 decode_player, ResEncode, (UserDecodeData)&PlayerLayout_M64, NULL };
115 #define FORMAT_PLAYER (&PlayerFormat)
116
117 //-------------------
118 // INTERNAL PROTOTYPES
119 //-------------------
120 errtype load_game_schedules(void);
121 errtype interpret_qvars(void);
122
123 #define OldResIdFromLevel(level) (OLD_SAVE_GAME_ID_BASE + (level * 2) + 2)
124
copy_file(char * src_fname,char * dest_fname)125 errtype copy_file(char *src_fname, char *dest_fname) {
126 FILE *fsrc, *fdst;
127 DEBUG("copy_file: %s to %s", src_fname, dest_fname);
128
129 fsrc = fopen_caseless(src_fname, "rb");
130 if (fsrc == NULL) {
131 return ERR_FOPEN;
132 }
133
134 fdst = fopen_caseless(dest_fname, "wb");
135 if (fdst == NULL) {
136 return ERR_FOPEN;
137 }
138
139 int b;
140 while ((b = fgetc(fsrc)) != EOF) {
141 fputc(b, fdst);
142 }
143
144 fclose(fsrc);
145 fclose(fdst);
146
147 return OK;
148 }
149
closedown_game(uchar visible)150 void closedown_game(uchar visible) {
151 extern void fr_closedown(void);
152 extern void olh_closedown(void);
153 extern void musicai_clear();
154 extern void drug_closedown(bool visible);
155 extern void hardware_closedown(bool visible);
156 extern void clear_digi_fx();
157 extern void reset_schedules(void);
158 extern void hud_shutdown_lines(void);
159 // clear any transient hud settings
160 hud_shutdown_lines();
161 drug_closedown(visible);
162 hardware_closedown(visible);
163 musicai_clear();
164 clear_digi_fx();
165 olh_closedown();
166 fr_closedown();
167 if (visible)
168 reset_schedules();
169 }
170
startup_game(uchar visible)171 void startup_game(uchar visible) {
172 extern void drug_startup(uchar visible);
173 extern void hardware_startup(uchar visible);
174 drug_startup(visible);
175 hardware_startup(visible);
176 if (visible) {
177 mfd_force_update();
178 side_icon_expose_all();
179 status_vitals_update(TRUE);
180 inventory_page = 0;
181 inv_last_page = INV_BLANK_PAGE;
182 }
183 }
184
185 #ifdef NOT_YET
186
check_save_game_wackiness(void)187 void check_save_game_wackiness(void) {
188 // for now, the only thing we have heard of is a bridge in general inventory
189 // so lets make sure geninv has only geninvable stuff
190 int i;
191 ObjID cur_test;
192 for (i = 0; i < NUM_GENERAL_SLOTS; i++) {
193 cur_test = player_struct.inventory[i];
194 #ifdef USELESS_OBJECT_CHECK
195 if (cur_test != OBJ_NULL) {
196 if ((ObjProps[OPNUM(cur_test)].flags & INVENTORY_GENERAL) == 0)
197 Warning(("You have obj %d a %d as the %d element of geninv, BADNESS\n", cur_test, OPNUM(cur_test), i));
198 // else
199 // Warning(("You have obj %d a %d as the %d element of geninv ok
200 // %x\n",cur_test,OPNUM(cur_test),i,ObjProps[OPNUM(cur_test)].flags));
201 }
202 #endif
203 }
204 }
205
206 #endif // NOT_YET
207
208 extern int flush_resource_cache();
209
save_game(char * fname,char * comment)210 errtype save_game(char *fname, char *comment) {
211 int filenum;
212 State player_state;
213 errtype retval;
214 int idx = SAVE_GAME_ID_BASE;
215
216 // KLC - this does nothing now. check_save_game_wackiness();
217 // Why is this done??? closedown_game(FALSE);
218
219 DEBUG("starting save_game");
220
221 // KLC do it the Mac way i = flush_resource_cache();
222 // Size dummy;
223 // MaxMem(&dummy); DG: I don't think this is needed anymore
224
225 // Open the current game file to save some more resources into it.
226 // FSMakeFSSpec(gDataVref, gDataDirID, CURRENT_GAME_FNAME, &currSpec);
227 filenum = ResEditFile(CURRENT_GAME_FNAME, FALSE);
228 if (filenum < 0) {
229 ERROR("Couldn't open Current Game");
230 return ERR_FOPEN;
231 }
232
233 // Sakeave comment
234 ResMake(idx, (void *)comment, strlen(comment) + 1, RTYPE_APP, filenum, RDF_LZW, FORMAT_RAW);
235 ResWrite(idx);
236 ResUnmake(idx);
237 idx++;
238
239 // Save player struct (resource #4001)
240 player_struct.version_num = PLAYER_VERSION_NUMBER;
241 player_struct.realspace_loc = objs[player_struct.rep].loc;
242 EDMS_get_state(objs[PLAYER_OBJ].info.ph, &player_state);
243 LG_memcpy(player_struct.edms_state, &player_state, sizeof(fix) * 12);
244 // LZW later ResMake(idx, (void *)&player_struct, sizeof(player_struct), RTYPE_APP, filenum,
245 // RDF_LZW);
246
247 ResMake(idx, (void *)&player_struct, sizeof(player_struct), RTYPE_APP, filenum, 0, FORMAT_PLAYER);
248 ResWrite(idx);
249 ResUnmake(idx);
250 idx++;
251
252 // HAX HAX HAX Skip the schedule for now!
253 // Save game schedule (resource #590)
254 idx = SCHEDULE_BASE_ID;
255 // LZW later ResMake(idx, (void *)&game_seconds_schedule, sizeof(Schedule), RTYPE_APP, filenum,
256 // RDF_LZW);
257
258 ResMake(idx, (void *)&game_seconds_schedule, sizeof(Schedule), RTYPE_APP, filenum, 0, FORMAT_SCHEDULE);
259 ResWrite(idx);
260 ResUnmake(idx);
261 idx++;
262
263 // Save game schedule vec info (resource #591)
264 // LZW later ResMake(idx, (void *)game_seconds_schedule.queue.vec, sizeof(SchedEvent)*GAME_SCHEDULE_SIZE,
265 // RTYPE_APP, filenum, RDF_LZW);
266 ResMake(idx, (void *)game_seconds_schedule.queue.vec, sizeof(SchedEvent) * GAME_SCHEDULE_SIZE, RTYPE_APP, filenum,
267 0, FORMAT_SCHEDULE_QUEUE);
268 ResWrite(idx);
269 ResUnmake(idx);
270 idx++;
271
272 ResCloseFile(filenum);
273
274 // Save current level
275 retval = write_level_to_disk(ResIdFromLevel(player_struct.level), TRUE);
276 if (retval) {
277 ERROR("Return value from write_level_to_disk is non-zero!"); //
278 critical_error(CRITERR_FILE | 3);
279 }
280
281 // Copy current game out to save game slot
282 if (copy_file(CURRENT_GAME_FNAME, fname) != OK) {
283 // Put up some alert here.
284 ERROR("No good copy, dude!");
285 // string_message_info(REF_STR_SaveGameFail);
286 }
287 // KLC else
288 // KLC string_message_info(REF_STR_SaveGameSaved);
289 old_ticks = *tmd_ticks;
290 // do we have to do this? startup_game(FALSE);
291 return (OK);
292 }
293
load_game_schedules(void)294 errtype load_game_schedules(void) {
295 extern int compare_events(void *, void *);
296 char *oldvec;
297 int idx = SCHEDULE_BASE_ID;
298
299 oldvec = game_seconds_schedule.queue.vec;
300 ResExtract(idx++, FORMAT_SCHEDULE, &game_seconds_schedule);
301 game_seconds_schedule.queue.vec = oldvec;
302 game_seconds_schedule.queue.comp = compare_events;
303 ResExtract(idx++, FORMAT_SCHEDULE_QUEUE, oldvec);
304 return OK;
305 }
306
interpret_qvars(void)307 errtype interpret_qvars(void) {
308 extern void recompute_music_level(ushort var);
309 extern void recompute_digifx_level(ushort var);
310 #ifdef AUDIOLOGS
311 extern void recompute_audiolog_level(ushort var);
312 #endif
313 #ifdef SVGA_SUPPORT
314 extern short mode_id;
315 #endif
316 extern void digichan_dealfunc(short val);
317 extern void dclick_dealfunc(ushort var);
318 extern void joysens_dealfunc(ushort var);
319 extern void language_change(uchar lang);
320 extern errtype load_da_palette();
321 extern uchar fullscrn_vitals;
322 extern uchar fullscrn_icons;
323 extern uchar map_notes_on;
324 extern uchar audiolog_setting;
325 extern char convert_use_mode;
326 extern ubyte hud_color_bank;
327
328 // KLC - don't do this here - it's a global now. load_da_palette();
329
330 gamma_dealfunc(QUESTVAR_GET(GAMMACOR_QVAR));
331
332 // dclick_dealfunc(QUESTVAR_GET(DCLICK_QVAR));
333 // joysens_dealfunc(QUESTVAR_GET(JOYSENS_QVAR));
334
335 recompute_music_level(QUESTVAR_GET(MUSIC_VOLUME_QVAR));
336 recompute_digifx_level(QUESTVAR_GET(SFX_VOLUME_QVAR));
337 #ifdef AUDIOLOGS
338 recompute_audiolog_level(QUESTVAR_GET(ALOG_VOLUME_QVAR));
339 //audiolog_setting = QUESTVAR_GET(ALOG_OPT_QVAR); //moved to prefs file
340 #endif
341 fullscrn_vitals = QUESTVAR_GET(FULLSCRN_VITAL_QVAR);
342 fullscrn_icons = QUESTVAR_GET(FULLSCRN_ICON_QVAR);
343 map_notes_on = QUESTVAR_GET(AMAP_NOTES_QVAR);
344 hud_color_bank = QUESTVAR_GET(HUDCOLOR_QVAR);
345
346 digichan_dealfunc(QUESTVAR_GET(DIGI_CHANNELS_QVAR));
347
348 // mouse_set_lefty(QUESTVAR_GET(MOUSEHAND_QVAR));
349
350 language_change(QUESTVAR_GET(LANGUAGE_QVAR));
351
352 return (OK);
353 }
354
355 // char saveArray[16]; //Â¥temp
356
load_game(char * fname)357 errtype load_game(char *fname) {
358 int filenum;
359 ObjID old_plr;
360 uchar bad_save = FALSE;
361 char orig_lvl;
362 extern errtype change_detail_level(byte new_level);
363 extern void player_set_eye_fixang(int ang);
364 extern uint dynmem_mask;
365
366 INFO("load_game %s", fname);
367
368 //see setup.c
369 extern void empty_slate(void);
370 empty_slate();
371
372 closedown_game(TRUE);
373 // KLC - don't do this here stop_music();
374
375 // Copy the save file into the current game
376 copy_file(fname, CURRENT_GAME_FNAME);
377
378 // Load in player and current level
379 filenum = ResOpenFile(CURRENT_GAME_FNAME);
380 old_plr = player_struct.rep;
381 orig_lvl = player_struct.level;
382
383 ResExtract(SAVE_GAME_ID_BASE + 1, FORMAT_PLAYER, (void *)&player_struct);
384
385 obj_check_time = 0; // KLC - added because it needs to be reset for Mac version.
386
387 // KLC - this is a global pref now. change_detail_level(player_struct.detail_level);
388 player_struct.rep = old_plr;
389 player_set_eye_fixang(player_struct.eye_pos);
390 if (!bad_save)
391 obj_move_to(PLAYER_OBJ, &(player_struct.realspace_loc), FALSE);
392
393 if (load_game_schedules() != OK)
394 bad_save = TRUE;
395
396 ResCloseFile(filenum);
397
398 if (orig_lvl == player_struct.level) {
399 // Warning(("HEY, trying to be clever about loading the game! %d vs %d\n",orig_lvl,player_struct.level));
400 dynmem_mask = DYNMEM_PARTIAL;
401 }
402
403 load_level_from_file(player_struct.level);
404 obj_load_art(FALSE); // KLC - added here (removed from load_level_data)
405 // KLC string_message_info(REF_STR_LoadGameLoaded);
406 dynmem_mask = DYNMEM_ALL;
407 chg_set_flg(_current_3d_flag);
408 old_ticks = *tmd_ticks;
409 interpret_qvars();
410 startup_game(FALSE);
411
412 // KLC - do following instead recompute_music_level(QUESTVAR_GET(MUSIC_VOLUME_QVAR));
413 if (music_on) {
414 mlimbs_on = TRUE;
415 mlimbs_AI_init();
416 mai_intro(); // KLC - added here
417 load_score_for_location(PLAYER_BIN_X, PLAYER_BIN_Y); // KLC - added here
418 }
419
420 // CC: Should we go back into fullscreen mode?
421 if (player_struct.hardwarez_status[CPTRIP(FULLSCR_HARD_TRIPLE)]) {
422 _new_mode = FULLSCREEN_LOOP;
423 chg_set_flg(GL_CHG_LOOP);
424 }
425
426 extern uchar muzzle_fire_light;
427 extern void lamp_turnon(uchar visible, uchar real);
428 extern void lamp_turnoff(uchar visible, uchar real);
429 muzzle_fire_light = FALSE;
430 if (!(player_struct.hardwarez_status[CPTRIP(LANTERN_HARD_TRIPLE)] & WARE_ON))
431 lamp_turnoff(TRUE, FALSE);
432 else
433 lamp_turnon(TRUE, FALSE);
434
435 //¥¥ temp
436 // BlockMove(0, saveArray, 16);
437
438 return (OK);
439 }
440
load_level_from_file(int level_num)441 errtype load_level_from_file(int level_num) {
442 errtype retval;
443
444 INFO("Loading save %i", level_num);
445
446 retval = load_current_map(ResIdFromLevel(level_num));
447
448 if (retval == OK) {
449 player_struct.level = level_num;
450
451 compute_shodometer_value(FALSE);
452
453 // if this is the first time the level is loaded, compute the inital shodan security level
454 if (player_struct.initial_shodan_vals[player_struct.level] == -1)
455 player_struct.initial_shodan_vals[player_struct.level] = QUESTVAR_GET(SHODAN_QV);
456 }
457
458 return (retval);
459 }
460
461 #ifdef NOT_YET //
462
check_and_update_initial(void)463 void check_and_update_initial(void) {
464 extern Datapath savegame_dpath;
465 char archive_fname[128];
466 char dpath_fn[50];
467 char *tmp;
468 extern char real_archive_fn[20];
469 if (!DatapathFind(&savegame_dpath, CURRENT_GAME_FNAME, archive_fname)) {
470 tmp = getenv("CITHOME");
471 if (tmp) {
472 strcpy(dpath_fn, tmp);
473 strcat(dpath_fn, "\\");
474 } else
475 dpath_fn[0] = '\0';
476 strcat(dpath_fn, "data\\");
477 strcat(dpath_fn, CURRENT_GAME_FNAME);
478
479 if (!DatapathFind(&DataDirPath, real_archive_fn, archive_fname))
480 critical_error(CRITERR_RES | 0x10);
481 if (copy_file(archive_fname, dpath_fn) != OK)
482 critical_error(CRITERR_FILE | 0x7);
483 }
484 }
485
486 #endif // NOT_YET
487
create_initial_game_func(short undefined1,ulong undefined2,void * undefined3)488 uchar create_initial_game_func(short undefined1, ulong undefined2, void *undefined3) {
489 int i;
490 extern int actual_score;
491 byte plrdiff[4];
492 char tmpname[sizeof(player_struct.name)];
493 short plr_obj;
494 extern errtype do_level_entry_triggers();
495
496 INFO("Starting game");
497 DEBUG("Game archive at %s", ARCHIVE_FNAME);
498
499 // Copy archive into local current game file.
500
501 if (copy_file(ARCHIVE_FNAME, CURRENT_GAME_FNAME) != OK)
502 critical_error(CRITERR_FILE | 7);
503
504 plr_obj = PLAYER_OBJ;
505 for (i = 0; i < 4; i++)
506 plrdiff[i] = player_struct.difficulty[i];
507 LG_memcpy(tmpname, player_struct.name, sizeof(tmpname));
508
509 // KLC - don't need this anymore. ResExtract(SAVE_GAME_ID_BASE + 1, (void *)&player_struct);
510
511 init_player(&player_struct);
512 obj_check_time = 0; // KLC - added here cause it needs to be reset in Mac version
513
514 player_struct.rep = OBJ_NULL;
515
516 load_level_from_file(player_struct.level);
517
518 obj_load_art(FALSE); // KLC - added here (removed from load_level_data)
519 amap_reset();
520
521 player_create_initial();
522
523 LG_memcpy(player_struct.name, tmpname, sizeof(player_struct.name));
524 for (i = 0; i < 4; i++)
525 player_struct.difficulty[i] = plrdiff[i];
526
527 // KLC - not needed any longer ResCloseFile(filenum);
528
529 // Reset MFDs to be consistent with starting setup
530 init_newmfd();
531
532 // No time elapsed, really, honest
533 old_ticks = *tmd_ticks;
534
535 // Setup some start-game stuff
536 // Music
537 current_score = actual_score = last_score = PERIL_SCORE; // KLC - these aren't actually
538 mlimbs_peril = 1000; // going to do anything.
539
540 if (music_on) {
541 mlimbs_on = TRUE;
542 mlimbs_AI_init();
543 mai_intro(); // KLC - added here
544 load_score_for_location(PLAYER_BIN_X, PLAYER_BIN_Y); // KLC - added here
545 }
546
547 load_dynamic_memory(DYNMEM_ALL);
548
549 // KLC - if not already on, turn on-line help on.
550 if (!olh_active)
551 toggle_olh_func(0, 0, 0);
552
553 // Do entry-level triggers for starting level
554 // Hmm, do we actually want to call this any time we restore
555 // a saved game or whatever? No, probably not....hmmm.....
556
557 do_level_entry_triggers();
558
559 // turn on help overlay.
560 olh_overlay_on = olh_active;
561
562 // Plot timers
563
564 return (FALSE);
565 }
566
write_level_to_disk(int idnum,uchar flush_mem)567 errtype write_level_to_disk(int idnum, uchar flush_mem) {
568 // Eventually, this ought to cleverly determine whether or not to pack
569 // the save game resource, but for now we will always do so...
570
571 // FSMakeFSSpec(gDataVref, gDataDirID, CURRENT_GAME_FNAME, &currSpec);
572
573 // char* currSpec = "saves/save.dat";
574 return (save_current_map(CURRENT_GAME_FNAME, idnum, flush_mem, TRUE));
575 }
576