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