/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * Additional copyright for this file: * Copyright (C) 1999-2000 Revolution Software Ltd. * This code is based on source code created by Revolution Software, * used with permission. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "engines/icb/icb.h" #include "engines/icb/common/px_common.h" #include "engines/icb/mission.h" #include "engines/icb/global_objects.h" #include "engines/icb/global_switches.h" #include "engines/icb/direct_input.h" #include "engines/icb/p4.h" #include "engines/icb/remora.h" #include "engines/icb/icon_list_manager.h" #include "engines/icb/sound.h" #include "engines/icb/string_vest.h" #include "engines/icb/sound_logic.h" #include "engines/icb/timer_func.h" #include "engines/icb/mission_functions.h" #include "engines/icb/common/datapacker.h" #include "engines/icb/res_man.h" #include "engines/icb/options_manager_pc.h" #include "engines/icb/cluster_manager_pc.h" #include "common/util.h" #include "common/textconsole.h" namespace ICB { // Short term global until we stop supporting old save game format uint32 packData; bool8 Setup_camtest_mission() { // create a null mission and session for camtest viewer mode return FALSE8; } void _mission::Set_new_session_name(const char *ascii) { // record the name of the next session /* // name to lower for (uint32 j = 0; j < strlen(ascii); j++) if (Common::isUpper(*(ascii + j))) *(ascii + j) = tolower(*(ascii + j));*/ Set_string(const_cast(ascii), new_session_name, TINY_NAME_LEN); new_session = TRUE8; // trigger a new session } void _mission::Set_init_nico_name(const char *ascii) { // record the name of the init nico Set_string(const_cast(ascii), init_nico_name, TINY_NAME_LEN); init_nico = TRUE8; // trigger a new session } bool8 _mission::Is_there_init_nico() { // return init nico status return (init_nico); } void _mission::Reset_init_nico() { // remove the name of the init nico init_nico = FALSE8; // reset } const char *_mission::Return_init_nico_name() { return ((const char *)init_nico_name); } bool8 Setup_new_mission(const char *mission_name, const char *session_name) { // Just to be tidy // Stop any previous sounds StopAllSoundsNow(); // Pause the sound engine PauseSounds(); // create a new mission object deleting any current one // fix names to lowercase on the PC ONLY /* uint32 j; for (j = 0; j < strlen(mission_name); j++) if (Common::isUpper(*(mission_name + j))) *(mission_name + j) = tolower(*(mission_name + j)); for (j = 0; j < strlen(session_name); j++) if (Common::isUpper(*(session_name + j))) *(session_name + j) = tolower(*(session_name + j));*/ // check if session exists // If we are using the straight paths check for the cluster // Otherwise make up the path to the psx name format cluster // Make the filename equivalent of the hash'ed version of mission name char h_mission_name[8]; HashFile(mission_name, h_mission_name); char h_session_name[8]; HashFile(session_name, h_session_name); // convert hashes to lower-case for FS operations for (int32 i = 0; i < 8; i++) { h_mission_name[i] = (char)tolower(h_mission_name[i]); h_session_name[i] = (char)tolower(h_session_name[i]); } Common::sprintf_s(temp_buf, SESSION_TEST_PATH, h_mission_name, h_session_name); #if 1 // was #ifdef FROM_PC_CD // Need the mission data present on hard-disk for it to destruct properly // so do this here for the pc before we shunt stuff about if (g_mission) g_icb_mission->___delete_mission(); // Call the runtime cluster manager to install mission data on minimum install // Need a mission id number fist of all MISSION_ID m = (MISSION_ID)FindMissionNumber(mission_name); // Initialise the install for this mission // This function also checks that the correct CD is in the drive too if (g_theClusterManager->StartMissionInstall(m)) { // Keep calling this until it's done. I know this prevents windows // from doing anything but that's a useful thing for installing stuff. while (g_theClusterManager->InstallMission()) ; } #endif if (rs_bg->Test_file(temp_buf)) { if (g_mission) g_icb_mission->___delete_mission(); g_icb_mission->___init_mission(mission_name, session_name); MS->Pre_initialise_objects(); MS->Init_objects(); return (TRUE8); } return (FALSE8); } void _mission::___init_mission(const char *new_mission_name, const char *session_name) { // mission object constructor // creates a session too Zdebug("___init_mission %s %s", new_mission_name, session_name); // camview mode if (camera_hack) return; chi_following = FALSE8; // store a mission wide session independent record of whether or not chi has been initialised num_bullets = 0; num_clips = 0; num_medi = 0; inited_globals = FALSE8; if (new_mission_name == nullptr) Fatal_error("new mission no mission name"); if (session_name == nullptr) Fatal_error("new mission no session name"); // Work out which CD we should be using g_px->current_cd = WhichCD(new_mission_name); // Need a mission id number fist of all MISSION_ID m = (MISSION_ID)FindMissionNumber(new_mission_name); // Initialise the install for this mission // This function also checks that the correct CD is in the drive too if (g_theClusterManager->StartMissionInstall(m)) { // Keep calling this until it's done. I know this prevents windows // from doing anything but that's a useful thing for installing stuff. while (g_theClusterManager->InstallMission()) ; } // When using clusters keep items withouth the root so the correct hashing // start point can be maintained Common::strcpy_s(mission_name, new_mission_name); Set_string(new_mission_name, tiny_mission_name, TINY_NAME_LEN); Set_string(session_name, tiny_session_name, TINY_NAME_LEN); // Make the filename equivalent of the hash'ed version of mission name HashFile(new_mission_name, h_mission_name); Zdebug("-- Begin new mission - %s", (const char *)new_mission_name); // setup mission sound LoadMissionSounds(new_mission_name); // Reset the widescreen and fade effects surface_manager->Reset_Effects(); // set pointer :this is so things inside session->__init can use MS e.g. remora & inventory initialisation g_mission = g_icb_mission; // set sessions private resource manager to have no defragging - will fail as soon as a file cannot fit private_session_resman->Set_to_no_defrag(); // setup the starting session // note - objects will need their initialise-objects call making outside of this because mission->session-> must be inited session = g_icb_session; session->___init(mission_name, session_name); // set mission status to ok - a script can reset to !0 to end the entire mission mission_status = 0; // camera will follow the player when this is 0 - change to another id to follow that instead camera_follow_id_overide = 0; // Don't think this matters but better be safe. remora_save_mode = -1; // total micro sessions saved number_sessions_saved = 0; // reset new session flag new_session = FALSE8; init_nico = FALSE8; Zdebug("~___init_mission %s %s", new_mission_name, session_name); } void _mission::___delete_mission() { // mission object deconstructor Zdebug("deleting mission"); // kill the pointer which doubles as an inited yes/no flag g_mission = nullptr; session->___destruct(); // Close any existing menu g_oIconMenu->CloseDownIconMenu(); // This kills any lists that are lying about (that script writers may have created but forgotten to destroy). It // won't kill the inventory though, because that has GAME_WIDE scope and we are only killing to MISSION_WIDE. g_oIconListManager->ResetToScopeLevel(MISSION_WIDE); // This is the call that removes all the icons in the inventory list WITHOUT destroying the list itself. g_oIconListManager->ResetList(ICON_LIST_INVENTORY); // Turn the in-game timer off StopTimer(); } void _mission::End_mission() { // cause the mission to end and shutdown mission_status = 1; } _mission::_mission() : session(nullptr), camera_follow_id_overide(0), remora_save_mode(0), ad_time(0), lt_time(0), set_time(0), flip_time(0), cycle_time(0), logic_time(0), resman_logic_time(0), los_time(0), event_time(0), sound_time(0), xtra_mega_time(0), nActorsDrawn(0), nActorsConsidered(0), old_hits_value(0), chi_following(0), num_bullets(0), num_clips(0), num_medi(0), inited_globals(FALSE8), mission_terminate(0), mission_status(0), number_sessions_saved(0), new_session(FALSE8), init_nico(FALSE8) { memset(new_session_name, '\0', TINY_NAME_LEN); memset(init_nico_name, '\0', TINY_NAME_LEN); memset(mission_name, '\0', ENGINE_STRING_LEN); memset(tiny_mission_name, '\0', TINY_NAME_LEN); memset(tiny_session_name, '\0', TINY_NAME_LEN); memset(h_mission_name, '\0', 8); for (int32 i = 0; i < MAX_sessions; i++) { memset(micro_sessions[i].session__name, '\0', ENGINE_STRING_LEN); micro_sessions[i].number_of_micro_objects = 0; for (int32 j = 0; j < MAX_session_objects; j++) { memset(micro_sessions[i].micro_objects[j].lvar_value, 0, MAX_lvars * sizeof(int32)); micro_sessions[i].micro_objects[j].total_lvars = 0; micro_sessions[i].micro_objects[j].status_flag = OB_STATUS_NOT_HELD; } memset(micro_sessions[i].fvars, 0, MAX_fvars * sizeof(int32)); } } _mission::~_mission() { Zdebug("*mission destructing*"); } uint32 _mission::Game_cycle() { // run a cycle for the current session in the current mission // returns 0 ok // 1 finish the mission // safety check for no session if (session == nullptr) Fatal_error("no session"); if (new_session == TRUE8) { // a new session has been requested PauseSounds(); // save the session data to a micro session Save_micro_session(); // remove existing session - taking the view with it session->___destruct(); // set sessions private resource manager to have no defragging - will fail as soon as a file cannot fit private_session_resman->Set_to_no_defrag(); // initialise the new one session->___init((const char *)mission_name, (const char *)new_session_name); // set the tiny session name Set_string(new_session_name, tiny_session_name, TINY_NAME_LEN); // create gameworld and structs session->Pre_initialise_objects(); // reload object values if the session has been saved Restore_micro_session_vars(); // init the objects session->Init_objects(); // reload object coordinates Restore_micro_session_coords(FALSE8); // must reset to NULL new_session = FALSE8; // cancel init nico g_mission->Reset_init_nico(); camera_follow_id_overide = 0; // cancel previous watch remora_save_mode = -1; UnpauseSounds(); } // help out rs_anims rs_anims->Garbage_removal(); // update the sound engine UpdateHearableSounds(); // now do a loop of logic // get start time MS->prev_save_state = MS->Can_save(); // get previous state - used by lifts to see if player is active MS->Set_can_save(FALSE8); // can't save as default - this is reversed by states that allow save this game cycle g_px->logic_timing = TRUE8; logic_time = GetMicroTimer(); resman_logic_time = 0; // reset xtra_mega_time = 0; session->One_logic_cycle(); // work out overall time logic_time = GetMicroTimer() - logic_time; g_px->logic_timing = FALSE8; // cancel SAVE if watching another mega if (g_mission->camera_follow_id_overide) MS->Set_can_save(FALSE8); session->Process_conveyors(); // conveyor belts // Update global timer by a tick g_globalScriptVariables->SetVariable("missionelapsedtime", g_globalScriptVariables->GetVariable("missionelapsedtime") + 1); // call the camera director which will pick a view depending upon the player objects position session->Camera_director(); return (mission_status); } void _mission::Save_micro_session() { // save all object lvars to a micro session CGame *object; uint32 j = 0; uint32 i, k; uint32 total_fvars = 0; Tdebug("micro_session.txt", "\n\nSAVING session %s", Fetch_tiny_session_name()); // see if this session has ever been saved for (j = 0; j < number_sessions_saved; j++) { if (!strcmp((const char *)(micro_sessions[j].session__name), Fetch_tiny_session_name())) { Tdebug("micro_session.txt", " session found - slot %d", j); break; } } // are we creating a new one if (j == number_sessions_saved) { number_sessions_saved++; } // j is slot number Set_string(Fetch_tiny_session_name(), micro_sessions[j].session__name); Tdebug("micro_session.txt", " saving in slot %d", j); // save number of em micro_sessions[j].number_of_micro_objects = session->Fetch_number_of_objects(); for (i = 0; i < session->Fetch_number_of_objects(); i++) { object = (CGame *)LinkedDataObject::Fetch_item_by_number(session->objects, i); Tdebug("micro_session.txt", "\n object %d %s, %d vars - status %d", i, CGameObject::GetName(object), CGameObject::GetNoLvars(object), session->Fetch_object_status(i)); micro_sessions[j].micro_objects[i].status_flag = session->Fetch_object_status(i); // if mega then save coord if (session->logic_structs[i]->image_type == VOXEL) { if (!session->logic_structs[i]->mega->pushed) { Tdebug("micro_session.txt", " mega"); micro_sessions[j].fvars[total_fvars++] = ((int32)session->logic_structs[i]->mega->actor_xyz.x); micro_sessions[j].fvars[total_fvars++] = ((int32)session->logic_structs[i]->mega->actor_xyz.y); micro_sessions[j].fvars[total_fvars++] = ((int32)session->logic_structs[i]->mega->actor_xyz.z); } else { Tdebug("micro_session.txt", " mega *pushed*"); micro_sessions[j].fvars[total_fvars++] = ((int32)session->logic_structs[i]->mega->pushed_actor_xyz.x); micro_sessions[j].fvars[total_fvars++] = ((int32)session->logic_structs[i]->mega->pushed_actor_xyz.y); micro_sessions[j].fvars[total_fvars++] = ((int32)session->logic_structs[i]->mega->pushed_actor_xyz.z); } micro_sessions[j].fvars[total_fvars++] = ((int32)(session->logic_structs[i]->pan * PAN_SCALE_FACTOR)); } micro_sessions[j].micro_objects[i].total_lvars = 0; if (CGameObject::GetNoLvars(object) > MAX_lvars) Fatal_error("object [%s] has too many lvars - has %d, only %d allowed", CGameObject::GetName(object), CGameObject::GetNoLvars(object), MAX_lvars); for (k = 0; k < CGameObject::GetNoLvars(object); k++) { if (!CGameObject::IsVariableString(object, k)) { Tdebug("micro_session.txt", " saving lvar %d %s value %d", k, CGameObject::GetScriptVariableName(object, k), CGameObject::GetIntegerVariable(object, k)); int32 value = CGameObject::GetIntegerVariable(object, k); // Using 14-bits to pack lvar's int32 packMin = -(1 << 13); int32 packMax = +((1 << 13) - 1); if ((value < packMin) || (value > packMax)) { // Don't do a message box for a CD build of the game! Message_box("Object '%s' lvar %d '%s' is too big to pack please try and reduce %d range is %d->%d", CGameObject::GetName(object), k, CGameObject::GetScriptVariableName(object, k), value, packMin, packMax); packData = 0; } // Using 16-bits as a maximum to store lvar's int32 lvarMin = -(1 << 15); int32 lvarMax = +((1 << 15) - 1); if ((value < lvarMin) || (value > lvarMax)) { Fatal_error("Object '%s' lvar %d '%s' is too big to save %d range is %d->%d", CGameObject::GetName(object), k, CGameObject::GetScriptVariableName(object, k), value, packMin, packMax); } micro_sessions[j].micro_objects[i].lvar_value[micro_sessions[j].micro_objects[i].total_lvars++] = value; } } } } void _mission::Restore_micro_session_vars() { // reload all object lvars from a micro session CGame *object; uint32 j = 0; uint32 i, k; uint32 lvar; Tdebug("micro_session.txt", "\n\nRestore_micro_session_vars session %s", Fetch_tiny_session_name()); // see if this session has ever been saved for (j = 0; j < number_sessions_saved; j++) if (!strcmp((const char *)(micro_sessions[j].session__name), Fetch_tiny_session_name())) { Tdebug("micro_session.txt", " session found - slot %d", j); // restore lvars for (i = 0; i < session->Fetch_number_of_objects(); i++) { object = (CGame *)LinkedDataObject::Fetch_item_by_number(session->objects, i); Tdebug("micro_session.txt", "\n object %d %s, %d vars - status %d", i, CGameObject::GetName(object), CGameObject::GetNoLvars(object), micro_sessions[j].micro_objects[i].status_flag); session->Set_object_status(i, micro_sessions[j].micro_objects[i].status_flag); lvar = 0; for (k = 0; k < CGameObject::GetNoLvars(object); k++) { if (!CGameObject::IsVariableString(object, k)) { Tdebug("micro_session.txt", " restoring lvar %d %s to %d", k, CGameObject::GetScriptVariableName(object, k), micro_sessions[j].micro_objects[i].lvar_value[lvar]); // reset lvar value CGameObject::SetIntegerVariable(object, k, micro_sessions[j].micro_objects[i].lvar_value[lvar++]); } } } return; } Tdebug("micro_session.txt", " session NOT found", j); } void _mission::Restore_micro_session_coords(bool8 from_disk) { // reload all object lvars from a micro session CGame *object; uint32 j = 0; uint32 i; uint32 index = 0; Tdebug("micro_session.txt", "\n\nRestore_micro_session_coords session %s", Fetch_tiny_session_name()); // see if this session has ever been saved for (j = 0; j < number_sessions_saved; j++) if (!strcmp((const char *)(micro_sessions[j].session__name), Fetch_tiny_session_name())) { Tdebug("micro_session.txt", " session found - slot %d", j); // restore lvars for (i = 0; i < session->Fetch_number_of_objects(); i++) { object = (CGame *)LinkedDataObject::Fetch_item_by_number(session->objects, i); Tdebug("micro_session.txt", "\n object %d %s, %d vars - status %d", i, CGameObject::GetName(object), CGameObject::GetNoLvars(object), micro_sessions[j].micro_objects[i].status_flag); session->Set_object_status(i, micro_sessions[j].micro_objects[i].status_flag); // if mega then restore coord if (session->logic_structs[i]->image_type == VOXEL) { // from disk, or doesn't have exclusives (or does have exclusives) but is chi and chi is not // following if ((from_disk) || (!session->logic_structs[i]->mega->has_exclusive_coords) || ((session->chi_id == i) && (session->chi_think_mode != __FOLLOWING))) { session->logic_structs[i]->mega->actor_xyz.x = (PXreal)micro_sessions[j].fvars[index++]; session->logic_structs[i]->mega->actor_xyz.y = (PXreal)micro_sessions[j].fvars[index++]; session->logic_structs[i]->mega->actor_xyz.z = (PXreal)micro_sessions[j].fvars[index++]; session->logic_structs[i]->pan = (PXreal)(micro_sessions[j].fvars[index++] / (PXreal)PAN_SCALE_FACTOR); } else { // skip as the data IS saved regardless index += 4; } } } return; } Tdebug("micro_session.txt", " session NOT found", j); } void _mission::Save_game_position(const char *filename, const char *slot_label, uint32 timeplayed) { // save the current game position to disk // save mission/session names // globals // micro-sessions uint32 i, j, k; int32 avalue, fval; int16 lval; uint8 atinyvalue; int32 l; uint32 numIcons; char *iconnames[ICON_LIST_MAX_ICONS]; uint32 iconHashes[ICON_LIST_MAX_ICONS]; uint32 iconCounts[ICON_LIST_MAX_ICONS]; Tdebug("save_restore.txt", "\nsaving file [%s]", filename); Tdebug("save_restore.txt", " mission [%s]", Fetch_tiny_mission_name()); // As a default we are trying to pack the data packData = 1; // save the CURRENT session data to a micro session Save_micro_session(); // Check the lvar's to see if we can pack them or not uint32 nlvars = 0; for (j = 0; j < number_sessions_saved; j++) { for (i = 0; i < micro_sessions[j].number_of_micro_objects; i++) { nlvars = (uint8)micro_sessions[j].micro_objects[i].total_lvars; for (k = 0; k < nlvars; k++) { lval = (int16)micro_sessions[j].micro_objects[i].lvar_value[k]; // Using 14-bits to pack lvar's int32 packMin = -(1 << 13); int32 packMax = +((1 << 13) - 1); if ((lval < packMin) || (lval > packMax)) { Message_box("Jake says : packData = 0"); packData = 0; } } } } // first save the index file which contains the session name and mission name that we're currently running - and hence want to // restore to later Common::WriteStream *stream = openDiskWriteStream(filename); // attempt to open the file for writing if (stream == nullptr) Fatal_error("Save_game_position cannot *OPEN* [%s]", (const char *)filename); // specific stuff for pc save game menu stream->write(slot_label, MAX_LABEL_LENGTH); stream->writeSint32LE(timeplayed); atinyvalue = SR_VERSION; stream->writeByte(atinyvalue); avalue = strlen(Fetch_tiny_mission_name()) + 1; stream->writeSint32LE(avalue); stream->write((const char *)Fetch_tiny_mission_name(), avalue); avalue = strlen(Fetch_tiny_session_name()) + 1; stream->writeSint32LE(avalue); stream->write((const char *)Fetch_tiny_session_name(), avalue); // now write the globals out atinyvalue = (uint8)g_globalScriptVariables->GetNoItems(); Tdebug("save_restore.txt", " %d globals", atinyvalue); stream->writeByte(atinyvalue); for (j = 0; j < atinyvalue; j++) { avalue = (int32)g_globalScriptVariables->GetVariable((*g_globalScriptVariables)[j].hash, nullptr, 0); stream->writeSint32LE(avalue); Tdebug("save_restore.txt", " %d 0x%08x = %d", j, (*g_globalScriptVariables)[j].hash, avalue); } // get the icon information numIcons = g_oIconListManager->GetList(ICON_LIST_INVENTORY, iconnames, iconHashes, iconCounts); stream->writeUint32LE(numIcons); Tdebug("save_restore.txt", " %d unique icons", numIcons); for (j = 0; j < numIcons; j++) { // write length of icon name then the string avalue = strlen(iconnames[j]) + 1; stream->writeSint32LE(avalue); stream->write((const void *)iconnames[j], avalue); // write the hash value avalue = (uint32)iconHashes[j]; stream->writeUint32LE(avalue); // write the count value atinyvalue = (uint8)iconCounts[j]; stream->writeByte(atinyvalue); } // save the micro-sessions // save number of micro-sessions avalue = number_sessions_saved; stream->writeSint32LE(avalue); Tdebug("save_restore.txt", " %d sessions", avalue); // The DataPacker object DataPacker dpack; for (j = 0; j < number_sessions_saved; j++) { // write the session name out Tdebug("save_restore.txt", " save session [%s]", (const char *)micro_sessions[j].session__name); avalue = strlen(micro_sessions[j].session__name) + 1; stream->writeSint32LE(avalue); Tdebug("save_restore.txt", " name len %d", avalue); stream->write((const void *)micro_sessions[j].session__name, avalue); for (l = 0; l < MAX_fvars; l++) { fval = micro_sessions[j].fvars[l]; stream->writeSint32LE(fval); Tdebug("save_restore.txt", " %d", fval); } stream->writeSint32LE(micro_sessions[j].number_of_micro_objects); Tdebug("save_restore.txt", " %d objects in session", avalue); for (i = 0; i < micro_sessions[j].number_of_micro_objects; i++) { Tdebug("save_restore.txt", " \nobject %d", i); // first write state flag atinyvalue = (uint8)micro_sessions[j].micro_objects[i].status_flag; Tdebug("save_restore.txt", " status %d", atinyvalue); stream->writeByte(atinyvalue); // save out lvars atinyvalue = (uint8)micro_sessions[j].micro_objects[i].total_lvars; stream->writeByte(atinyvalue); Tdebug("save_restore.txt", " %d lvars", atinyvalue); } // Pack the lvar data using the data packer object // Initialise the data packer object for writing DataPacker::ReturnCodes dret = DataPacker::OK; // Are we storing the raw data or using the packer ? DataPacker::PackModeEnum packMode = DataPacker::PACK; atinyvalue = 0; if (packData == 1) { packMode = DataPacker::PACK; atinyvalue = 1; } else { packMode = DataPacker::DONT_PACK; atinyvalue = 0; } stream->writeByte(atinyvalue); dret = dpack.open(DataPacker::WRITE, packMode); if (dret != DataPacker::OK) { Real_Fatal_error("DataPacker::Open failed dret %d", (int32)dret); } for (i = 0; i < micro_sessions[j].number_of_micro_objects; i++) { nlvars = micro_sessions[j].micro_objects[i].total_lvars; for (k = 0; k < nlvars; k++) { lval = (int16)micro_sessions[j].micro_objects[i].lvar_value[k]; Tdebug("save_restore.txt", " lvar %d = %d", k, lval); dret = dpack.put(lval, stream); if (dret != DataPacker::OK) { Real_Fatal_error("DataPacker::Put failed dret %d", (int32)dret); } } } dret = dpack.close(stream); if (dret != DataPacker::OK) { Real_Fatal_error("DataPacker::Close failed dret %d", (int32)dret); } } // now save special chi follow information if ((session->is_there_a_chi) && (session->chi_think_mode != __NOTHING) && (session->chi_history != session->cur_history)) { // there is a chi and she is following and not on our floor atinyvalue = 1; // yes Tdebug("save_restore.txt", "\nsaving CHI data"); stream->writeByte(atinyvalue); // save x,y and z to restart chi @ fval = (uint32)session->hist_pin_x; stream->writeSint32LE(fval); Tdebug("save_restore.txt", " %d", fval); fval = (uint32)session->hist_pin_y; stream->writeSint32LE(fval); Tdebug("save_restore.txt", " %d", fval); fval = (uint32)session->hist_pin_z; stream->writeSint32LE(fval); Tdebug("save_restore.txt", " %d", fval); } else { // no chi atinyvalue = 0; // no Tdebug("save_restore.txt", "\nNOT saving CHI data"); stream->writeByte(atinyvalue); } // save ammo, bullets, medis atinyvalue = (uint8)g_mission->num_bullets; stream->writeByte(atinyvalue); atinyvalue = (uint8)g_mission->num_clips; stream->writeByte(atinyvalue); atinyvalue = (uint8)g_mission->num_medi; stream->writeByte(atinyvalue); // manual cameras if (session->manual_camera) { atinyvalue = 1; // yes Tdebug("save_restore.txt", "\nsaving manual camera"); stream->writeByte(atinyvalue); // save name stream->write(session->manual_camera_name, ENGINE_STRING_LEN); // cam number stream->writeUint32LE(session->cur_camera_number); } else { // no manual camera atinyvalue = 0; // no Tdebug("save_restore.txt", "\nNOT saving manual camera"); stream->writeByte(atinyvalue); } // game script pc avalue = gs.pc; stream->writeSint32LE(avalue); Tdebug("save_restore.txt", " gamescript pc %d", avalue); // save timed events g_oEventManager->Save(stream); if (g_icb->getGameType() == GType_ICB) { // Save the Remora's locations-visited information. g_oRemora->Save(stream); } // save gfx init info for initing a set... surface_manager->SaveGFXInfo(stream); SavePlatformSpecific(stream); delete stream; } void _mission::Restore_micro_session_from_save_game(Common::SeekableReadStream *stream) { // restore the micro-sessions for the mission uint32 i, j, k; int32 avalue; uint8 atinyvalue; char buf[256]; uint32 total_sessions; uint8 total_lvars = 0; total_sessions = stream->readUint32LE(); Tdebug("save_restore.txt", " %d sessions", total_sessions); // set the total saved flag number_sessions_saved = total_sessions; // The DataPacker object DataPacker dpack; for (j = 0; j < total_sessions; j++) { // read the session name // length of name avalue = stream->readSint32LE(); // name stream->read(buf, avalue); Set_string(buf, micro_sessions[j].session__name); Tdebug("save_restore.txt", " load session [%s]", (const char *)micro_sessions[j].session__name); for (k = 0; k < MAX_fvars; k++) { avalue = stream->readSint32LE(); Tdebug("save_restore.txt", " %d", avalue); micro_sessions[j].fvars[k] = avalue; } micro_sessions[j].number_of_micro_objects = stream->readUint32LE(); Tdebug("save_restore.txt", " %d objects in session", micro_sessions[j].number_of_micro_objects); DataPacker::PackModeEnum packMode = DataPacker::PACK; // The new way for (i = 0; i < micro_sessions[j].number_of_micro_objects; i++) { Tdebug("save_restore.txt", " \nobject %d", i); // read in state flag atinyvalue = stream->readByte(); micro_sessions[j].micro_objects[i].status_flag = (_object_status)atinyvalue; Tdebug("save_restore.txt", " status %d", atinyvalue); // read in lvars total_lvars = stream->readByte(); Tdebug("save_restore.txt", " %d lvars", total_lvars); micro_sessions[j].micro_objects[i].total_lvars = (uint32)total_lvars; } atinyvalue = stream->readByte(); if (atinyvalue == 1) { packMode = DataPacker::PACK; } else { packMode = DataPacker::DONT_PACK; } // read in info for all objects // Unpack the lvar data using the data packer object // Initialise the data packer object for reading DataPacker::ReturnCodes dret = DataPacker::OK; dret = dpack.open(DataPacker::READ, packMode); if (dret != DataPacker::OK) { Real_Fatal_error("DataPacker::Open failed dret %d", (int32)dret); } for (i = 0; i < micro_sessions[j].number_of_micro_objects; i++) { total_lvars = (uint8)micro_sessions[j].micro_objects[i].total_lvars; for (k = 0; k < total_lvars; k++) { dret = dpack.Get(avalue, stream); if (dret != DataPacker::OK) { Real_Fatal_error("DataPacker::Get failed dret %d", (int32)dret); } micro_sessions[j].micro_objects[i].lvar_value[k] = avalue; Tdebug("save_restore.txt", " lvar %d = %d", k, avalue); } } dret = dpack.close(stream); if (dret != DataPacker::OK) { Real_Fatal_error("DataPacker::Close failed dret %d", (int32)dret); } } } __load_result Load_game(const char *filename) { // load a save game Common::SeekableReadStream *stream = nullptr; // file pointer uint32 avalue; uint8 atinyvalue; char mission_name[64]; char session_name[64]; char icon_name[MAXLEN_ICON_NAME]; uint32 icon_hash; uint32 j; uint32 num_icons; int32 fvar; Tdebug("save_restore.txt", "\nLoad game [%s]", filename); // open the index file stream = openDiskFileForBinaryStreamRead(filename); // attempt to open the file for reading if (stream == nullptr) return __NO_SUCH_FILE; char label[MAX_LABEL_LENGTH]; // load into here cause i'm too thick to know how to skip it (tw) fvar = stream->readUint32LE(); // load and discard time played stream->read(&label, MAX_LABEL_LENGTH); // load and discard user label name // load schema and check atinyvalue = stream->readByte(); if (atinyvalue != SR_VERSION) { delete stream; Real_Message_box("Old version save games are not supported file:%d code:%d", atinyvalue, SR_VERSION); return __WRONG_VERSION; } // read the mission name // length of name avalue = stream->readUint32LE(); // name stream->read(mission_name, avalue); // read the session name // length of name avalue = stream->readUint32LE(); // name stream->read(session_name, avalue); Tdebug("save_restore.txt", "mission [%s] session [%s]", mission_name, session_name); // number of globals atinyvalue = stream->readByte(); Tdebug("save_restore.txt", " %d globals", atinyvalue); if (atinyvalue != (uint8)g_globalScriptVariables->GetNoItems()) { Tdebug("save_restore.txt", " globals mismatch"); return __GLOBAL_MISMATCH; } for (j = 0; j < atinyvalue; j++) { avalue = stream->readSint32LE(); g_globalScriptVariables->SetVariable((*g_globalScriptVariables)[j].hash, avalue); Tdebug("save_restore.txt", " %d = %d", j, avalue); } // delete any existing mission if (g_mission) g_icb_mission->___delete_mission(); g_icb_mission->___init_mission(mission_name, session_name); // read number of icons num_icons = stream->readUint32LE(); Tdebug("save_restore.txt", " %d icons", num_icons); for (j = 0; j < num_icons; j++) { // string length avalue = stream->readSint32LE(); stream->read(icon_name, avalue); // read hash value icon_hash = stream->readUint32LE(); // read count atinyvalue = stream->readByte(); Tdebug("save_restore.txt", " [%s] hash %X %d", icon_name, icon_hash, atinyvalue); while (atinyvalue--) g_oIconListManager->AddIconToList(ICON_LIST_INVENTORY, icon_name, icon_hash); } // setup the major session components MS->Pre_initialise_objects(); // rebuild the micro-session g_mission->Restore_micro_session_from_save_game(stream); Tdebug("save_restore.txt", "restored micro session"); // reload object values if the session has been saved g_mission->Restore_micro_session_vars(); Tdebug("save_restore.txt", "restored vars"); // run the init scripts MS->Init_objects(); Tdebug("save_restore.txt", "initialised objects"); // reload object coordinates g_mission->Restore_micro_session_coords(TRUE8); MS->Set_init_voxel_floors(); // v important for some logics that will begin by checking a floor number // setup player history to new restored coordinate/floor MS->pre_interact_floor = MS->logic_structs[MS->player.Fetch_player_id()]->owner_floor_rect; // restore chi if she was following atinyvalue = stream->readByte(); if (atinyvalue) { Tdebug("save_restore.txt", "\nrestore CHI"); // chi x fvar = stream->readSint32LE(); Tdebug("save_restore.txt", " %d", fvar); MS->logic_structs[MS->chi_id]->mega->actor_xyz.x = (PXreal)fvar; // y fvar = stream->readSint32LE(); Tdebug("save_restore.txt", " %d", fvar); MS->logic_structs[MS->chi_id]->mega->actor_xyz.y = (PXreal)fvar; // z fvar = stream->readSint32LE(); Tdebug("save_restore.txt", " %d", fvar); MS->logic_structs[MS->chi_id]->mega->actor_xyz.z = (PXreal)fvar; g_mission->chi_following = TRUE8; // she was following so set the master flag - may or may not be required but lets go for safety Tdebug("save_restore.txt", " %3.1f %3.1f %3.1f", MS->logic_structs[MS->chi_id]->mega->actor_xyz.x, MS->logic_structs[MS->chi_id]->mega->actor_xyz.y, MS->logic_structs[MS->chi_id]->mega->actor_xyz.z); } else { Tdebug("save_restore.txt", "\nNOT restoring CHI"); } // save ammo, bullets, medis atinyvalue = stream->readByte(); g_mission->num_bullets = (uint32)atinyvalue; atinyvalue = stream->readByte(); g_mission->num_clips = (uint32)atinyvalue; atinyvalue = stream->readByte(); g_mission->num_medi = (uint32)atinyvalue; // manual camera? atinyvalue = stream->readByte(); if (atinyvalue) { Tdebug("save_restore.txt", "\nrestoring manual camera"); // save name stream->read(MS->manual_camera_name, ENGINE_STRING_LEN); // cam number MS->cur_camera_number = stream->readUint32LE(); MS->manual_camera = TRUE8; char h_buf[8]; HashFile(MS->manual_camera_name, h_buf); MS->Initialise_set(MS->manual_camera_name, h_buf); } else { Tdebug("save_restore.txt", "\nNOT restoring manual camera"); } // gamescript pc gs.pc = stream->readUint32LE(); // timed events g_oEventManager->Restore(stream); if (g_icb->getGameType() == GType_ICB) { // Restore the Remora's knowledge about where the player has been. g_oRemora->Restore(stream); } // load gfx init info for initing a set... surface_manager->LoadGFXInfo(stream); g_mission->LoadPlatformSpecific(stream); delete stream; // run a logic cycle to get those anims caching! MS->One_logic_cycle(); MS->Camera_director(); MS->One_logic_cycle(); return __LOAD_OK; } void _mission::Create_display() { switch (g_px->display_mode) { case THREED: // Need this for development safey - but is redundant in final (console-less) game if (!session->SetOK()) { g_px->display_mode = TEMP_NETHACK; // so we can bounce out again if a real camera/set is found // Save the actor's control mode session->player.Push_control_mode(ACTOR_RELATIVE); } else { // Check if we have just regain the focus after task switching if (gRegainedFocus) { session->set.ReInit(); if (g_oIconMenu->IsActive()) g_oIconMenu->ReActivate(); gRegainedFocus = false; } // To get on,off camera events in REMORA mode // this function is less accurate than stage_draw computation of same events session->UpdateOnOffCamera(); // The Remora has a function which gets called every cycle when it is active. This is because game // logic continues to run when the Remora is up. // But, note the background is NOT drawn whilst in REMORA mode if (g_icb->getGameType() == GType_ICB && g_oRemora->IsActive()) { g_oRemora->DrawRemora(); } else if (g_icb->getGameType() != GType_ICB && /*g_oMap*/g_oRemora->IsActive()) { //g_oMap.DrawMap(); } else { // full 3d stage draw NOT in REMORA mode session->Stage_draw_poly(); // Only render speech when not in REMORA mode // (as REMORA uses speech system to draw its own text) if (g_px->on_screen_text) { session->Render_speech(session->text_speech_bloc); // If there is currently a SFX subtitle active then display it. if (g_oSoundLogicEngine->SubtitleActive()) g_oSoundLogicEngine->DrawSubtitle(); } } // draw a timer if we have one... DrawTimer(); // If the icon menu is active, draw it. if (g_oIconMenu->IsActive()) { g_oIconMenu->DrawIconMenu(); if (g_icb->getGameType() == GType_ICB) { // If not in the REMORA then draw the armed menu & health bar as well if ((g_oRemora->IsActive() == FALSE8) && (session->logic_structs[session->player.Fetch_player_id()]->mega->Fetch_armed_status())) { int32 nBullets = session->player.GetNoBullets(); int32 nClips = session->player.GetNoAmmoClips(); int32 maxBullets = session->player.GetBulletsPerClip(); int32 maxClips = session->player.GetMaxClips(); g_oIconMenu->DrawArmedMenu(nBullets, maxBullets, nClips, maxClips); session->Draw_health_bar(); session->health_time = 0; // cancel the health bar timer } } } else if (session->logic_structs[session->player.Fetch_player_id()]->mega->Fetch_armed_status()) { // if player armed session->Draw_health_bar(); session->health_time = 0; // cancel the health bar timer int32 nBullets = session->player.GetNoBullets(); int32 nClips = session->player.GetNoAmmoClips(); int32 maxBullets = session->player.GetBulletsPerClip(); int32 maxClips = session->player.GetMaxClips(); g_oIconMenu->DrawArmedMenu(nBullets, maxBullets, nClips, maxClips); } else if (session->health_time) { // Draw the health bar if unarmed and recently taken damage session->health_time--; session->Draw_health_bar(); } if (g_icb->getGameType() == GType_ICB) { // If the icon menu is currently flashing added medipacks or clips draw it (but not in Remora). if (!g_oRemora->IsActive() && g_oIconMenu->IsAdding()) g_oIconMenu->DrawAdding(); } if (g_px->mega_timer) session->Display_mega_times(); if (true) { // FIXME: Enable this with debug level / channel session->player.Render_crude_interact_highlight(); session->Show_lit_unlit_diagnostics(); } session->player.DrawCompass(); } break; default: Fatal_error("unknown game display mode"); break; } } } // End of namespace ICB