Initial commit
This commit is contained in:
523
engines/ags/engine/game/game_init.cpp
Normal file
523
engines/ags/engine/game/game_init.cpp
Normal file
@@ -0,0 +1,523 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/engine/ac/character.h"
|
||||
#include "ags/engine/ac/dialog.h"
|
||||
#include "ags/engine/ac/display.h"
|
||||
#include "ags/engine/ac/draw.h"
|
||||
#include "ags/engine/ac/file.h"
|
||||
#include "ags/engine/ac/game.h"
|
||||
#include "ags/engine/ac/game_setup.h"
|
||||
#include "ags/shared/ac/game_setup_struct.h"
|
||||
#include "ags/engine/ac/game_state.h"
|
||||
#include "ags/engine/ac/gui.h"
|
||||
#include "ags/engine/ac/lip_sync.h"
|
||||
#include "ags/engine/ac/move_list.h"
|
||||
#include "ags/engine/ac/dynobj/all_dynamic_classes.h"
|
||||
#include "ags/engine/ac/dynobj/all_script_classes.h"
|
||||
#include "ags/engine/ac/dynobj/dynobj_manager.h"
|
||||
#include "ags/shared/ac/view.h"
|
||||
#include "ags/shared/core/asset_manager.h"
|
||||
#include "ags/engine/debugging/debug_log.h"
|
||||
#include "ags/shared/debugging/out.h"
|
||||
#include "ags/shared/font/ags_font_renderer.h"
|
||||
#include "ags/shared/font/fonts.h"
|
||||
#include "ags/engine/game/game_init.h"
|
||||
#include "ags/shared/gfx/bitmap.h"
|
||||
#include "ags/engine/gfx/ddb.h"
|
||||
#include "ags/shared/gui/gui_label.h"
|
||||
#include "ags/shared/gui/gui_inv.h"
|
||||
#include "ags/engine/media/audio/audio_system.h"
|
||||
#include "ags/engine/platform/base/ags_platform_driver.h"
|
||||
#include "ags/plugins/plugin_engine.h"
|
||||
#include "ags/shared/script/cc_common.h"
|
||||
#include "ags/engine/script/exports.h"
|
||||
#include "ags/engine/script/script.h"
|
||||
#include "ags/engine/script/script_runtime.h"
|
||||
#include "ags/shared/util/string_compat.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
#include "ags/engine/media/audio/audio_system.h"
|
||||
#include "ags/globals.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace Shared;
|
||||
using namespace Engine;
|
||||
|
||||
namespace AGS {
|
||||
namespace Engine {
|
||||
|
||||
String GetGameInitErrorText(GameInitErrorType err) {
|
||||
switch (err) {
|
||||
case kGameInitErr_NoError:
|
||||
return "No error.";
|
||||
case kGameInitErr_NoFonts:
|
||||
return "No fonts specified to be used in this game.";
|
||||
case kGameInitErr_TooManyAudioTypes:
|
||||
return "Too many audio types for this engine to handle.";
|
||||
case kGameInitErr_EntityInitFail:
|
||||
return "Failed to initialize game entities.";
|
||||
case kGameInitErr_PluginNameInvalid:
|
||||
return "Plugin name is invalid.";
|
||||
case kGameInitErr_NoGlobalScript:
|
||||
return "No global script in game.";
|
||||
case kGameInitErr_ScriptLinkFailed:
|
||||
return "Script link failed.";
|
||||
}
|
||||
return "Unknown error.";
|
||||
}
|
||||
|
||||
// Initializes audio channels and clips and registers them in the script system
|
||||
void InitAndRegisterAudioObjects(GameSetupStruct &game) {
|
||||
for (int i = 0; i < game.numCompatGameChannels; ++i) {
|
||||
_G(scrAudioChannel)[i].id = i;
|
||||
ccRegisterManagedObject(&_G(scrAudioChannel)[i], &_GP(ccDynamicAudio));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < game.audioClips.size(); ++i) {
|
||||
// Note that as of 3.5.0 data format the clip IDs are still restricted
|
||||
// to actual item index in array, so we don't make any difference
|
||||
// between game versions, for now.
|
||||
game.audioClips[i].id = i;
|
||||
ccRegisterManagedObject(&game.audioClips[i], &_GP(ccDynamicAudioClip));
|
||||
ccAddExternalScriptObject(game.audioClips[i].scriptName, &game.audioClips[i], &_GP(ccDynamicAudioClip));
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes characters and registers them in the script system
|
||||
void InitAndRegisterCharacters(GameSetupStruct &game) {
|
||||
for (int i = 0; i < game.numcharacters; ++i) {
|
||||
game.chars[i].walking = 0;
|
||||
game.chars[i].animating = 0;
|
||||
game.chars[i].pic_xoffs = 0;
|
||||
game.chars[i].pic_yoffs = 0;
|
||||
game.chars[i].blinkinterval = 140;
|
||||
game.chars[i].blinktimer = game.chars[i].blinkinterval;
|
||||
game.chars[i].index_id = i;
|
||||
game.chars[i].blocking_width = 0;
|
||||
game.chars[i].blocking_height = 0;
|
||||
game.chars[i].prevroom = -1;
|
||||
game.chars[i].loop = 0;
|
||||
game.chars[i].frame = 0;
|
||||
game.chars[i].walkwait = -1;
|
||||
ccRegisterManagedObject(&game.chars[i], &_GP(ccDynamicCharacter));
|
||||
|
||||
// export the character's script object
|
||||
ccAddExternalScriptObject(game.chars2[i].scrname_new, &game.chars[i], &_GP(ccDynamicCharacter));
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes dialog and registers them in the script system
|
||||
void InitAndRegisterDialogs(GameSetupStruct &game) {
|
||||
_GP(scrDialog).resize(MAX(1, game.numdialog)); // ensure at least 1 element, we must register buffer
|
||||
for (int i = 0; i < game.numdialog; ++i) {
|
||||
_GP(scrDialog)[i].id = i;
|
||||
_GP(scrDialog)[i].reserved = 0;
|
||||
ccRegisterManagedObject(&_GP(scrDialog)[i], &_GP(ccDynamicDialog));
|
||||
|
||||
if (!game.dialogScriptNames[i].IsEmpty())
|
||||
ccAddExternalScriptObject(game.dialogScriptNames[i], &_GP(scrDialog)[i], &_GP(ccDynamicDialog));
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes dialog options rendering objects and registers them in the script system
|
||||
void InitAndRegisterDialogOptions() {
|
||||
ccRegisterManagedObject(&_GP(ccDialogOptionsRendering), &_GP(ccDialogOptionsRendering));
|
||||
|
||||
_G(dialogOptionsRenderingSurface) = new ScriptDrawingSurface();
|
||||
_G(dialogOptionsRenderingSurface)->isLinkedBitmapOnly = true;
|
||||
long dorsHandle = ccRegisterManagedObject(_G(dialogOptionsRenderingSurface), _G(dialogOptionsRenderingSurface));
|
||||
ccAddObjectReference(dorsHandle);
|
||||
}
|
||||
|
||||
// Initializes gui and registers them in the script system
|
||||
HError InitAndRegisterGUI(GameSetupStruct &game) {
|
||||
_GP(scrGui).resize(MAX(1, game.numgui)); // ensure at least 1 element, we must register buffer
|
||||
for (int i = 0; i < game.numgui; ++i) {
|
||||
_GP(scrGui)[i].id = -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < game.numgui; ++i) {
|
||||
// link controls to their parent guis
|
||||
HError err = _GP(guis)[i].RebuildArray();
|
||||
if (!err)
|
||||
return err;
|
||||
// export all the GUI's controls
|
||||
export_gui_controls(i);
|
||||
_GP(scrGui)[i].id = i;
|
||||
ccAddExternalScriptObject(_GP(guis)[i].Name, &_GP(scrGui)[i], &_GP(ccDynamicGUI));
|
||||
ccRegisterManagedObject(&_GP(scrGui)[i], &_GP(ccDynamicGUI));
|
||||
}
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
// Initializes inventory items and registers them in the script system
|
||||
void InitAndRegisterInvItems(GameSetupStruct &game) {
|
||||
for (int i = 0; i < MAX_INV; ++i) {
|
||||
_G(scrInv)[i].id = i;
|
||||
_G(scrInv)[i].reserved = 0;
|
||||
ccRegisterManagedObject(&_G(scrInv)[i], &_GP(ccDynamicInv));
|
||||
|
||||
if (!game.invScriptNames[i].IsEmpty())
|
||||
ccAddExternalScriptObject(game.invScriptNames[i], &_G(scrInv)[i], &_GP(ccDynamicInv));
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes room hotspots and registers them in the script system
|
||||
void InitAndRegisterHotspots() {
|
||||
for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) {
|
||||
_G(scrHotspot)[i].id = i;
|
||||
_G(scrHotspot)[i].reserved = 0;
|
||||
ccRegisterManagedObject(&_G(scrHotspot)[i], &_GP(ccDynamicHotspot));
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes room objects and registers them in the script system
|
||||
void InitAndRegisterRoomObjects() {
|
||||
for (int i = 0; i < MAX_ROOM_OBJECTS; ++i) {
|
||||
ccRegisterManagedObject(&_G(scrObj)[i], &_GP(ccDynamicObject));
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes room regions and registers them in the script system
|
||||
void InitAndRegisterRegions() {
|
||||
for (int i = 0; i < MAX_ROOM_REGIONS; ++i) {
|
||||
_G(scrRegion)[i].id = i;
|
||||
_G(scrRegion)[i].reserved = 0;
|
||||
ccRegisterManagedObject(&_G(scrRegion)[i], &_GP(ccDynamicRegion));
|
||||
}
|
||||
}
|
||||
|
||||
// Registers static entity arrays in the script system
|
||||
void RegisterStaticArrays(GameSetupStruct &game) {
|
||||
_GP(StaticCharacterArray).Create(&_GP(ccDynamicCharacter), sizeof(CharacterInfo), sizeof(CharacterInfo));
|
||||
_GP(StaticObjectArray).Create(&_GP(ccDynamicObject), sizeof(ScriptObject), sizeof(ScriptObject));
|
||||
_GP(StaticGUIArray).Create(&_GP(ccDynamicGUI), sizeof(ScriptGUI), sizeof(ScriptGUI));
|
||||
_GP(StaticHotspotArray).Create(&_GP(ccDynamicHotspot), sizeof(ScriptHotspot), sizeof(ScriptHotspot));
|
||||
_GP(StaticRegionArray).Create(&_GP(ccDynamicRegion), sizeof(ScriptRegion), sizeof(ScriptRegion));
|
||||
_GP(StaticInventoryArray).Create(&_GP(ccDynamicInv), sizeof(ScriptInvItem), sizeof(ScriptInvItem));
|
||||
_GP(StaticDialogArray).Create(&_GP(ccDynamicDialog), sizeof(ScriptDialog), sizeof(ScriptDialog));
|
||||
|
||||
ccAddExternalStaticArray("character", &game.chars[0], &_GP(StaticCharacterArray));
|
||||
ccAddExternalStaticArray("object", &_G(scrObj)[0], &_GP(StaticObjectArray));
|
||||
ccAddExternalStaticArray("gui", &_GP(scrGui)[0], &_GP(StaticGUIArray));
|
||||
ccAddExternalStaticArray("hotspot", &_G(scrHotspot)[0], &_GP(StaticHotspotArray));
|
||||
ccAddExternalStaticArray("region", &_G(scrRegion)[0], &_GP(StaticRegionArray));
|
||||
ccAddExternalStaticArray("inventory", &_G(scrInv)[0], &_GP(StaticInventoryArray));
|
||||
ccAddExternalStaticArray("dialog", &_GP(scrDialog)[0], &_GP(StaticDialogArray));
|
||||
}
|
||||
|
||||
// Initializes various game entities and registers them in the script system
|
||||
HError InitAndRegisterGameEntities(GameSetupStruct &game) {
|
||||
InitAndRegisterAudioObjects(game);
|
||||
InitAndRegisterCharacters(game);
|
||||
InitAndRegisterDialogs(game);
|
||||
InitAndRegisterDialogOptions();
|
||||
HError err = InitAndRegisterGUI(game);
|
||||
if (!err)
|
||||
return err;
|
||||
InitAndRegisterInvItems(game);
|
||||
|
||||
InitAndRegisterHotspots();
|
||||
InitAndRegisterRegions();
|
||||
InitAndRegisterRoomObjects();
|
||||
|
||||
RegisterStaticArrays(game);
|
||||
|
||||
setup_player_character(game.playercharacter);
|
||||
if (_G(loaded_game_file_version) >= kGameVersion_270)
|
||||
ccAddExternalScriptObject("player", &_G(sc_PlayerCharPtr), &_GP(GlobalStaticManager));
|
||||
return HError::None();
|
||||
}
|
||||
|
||||
void LoadFonts(GameSetupStruct &game, GameDataVersion data_ver) {
|
||||
for (int i = 0; i < _GP(game).numfonts; ++i) {
|
||||
FontInfo &finfo = _GP(game).fonts[i];
|
||||
if (!load_font_size(i, finfo))
|
||||
quitprintf("Unable to load font %d, no renderer could load a matching file", i);
|
||||
|
||||
const bool is_wfn = is_bitmap_font(i);
|
||||
// Outline thickness corresponds to 1 game pixel by default;
|
||||
// but if it's a scaled up bitmap font, then it equals to scale
|
||||
if (data_ver < kGameVersion_360) {
|
||||
if (is_wfn && (finfo.Outline == FONT_OUTLINE_AUTO)) {
|
||||
set_font_outline(i, FONT_OUTLINE_AUTO, FontInfo::kSquared, get_font_scaling_mul(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Additional fixups - after all the fonts are registered
|
||||
for (int i = 0; i < _GP(game).numfonts; ++i) {
|
||||
if (!is_bitmap_font(i)) {
|
||||
// Check for the LucasFan font since it comes with an outline font that
|
||||
// is drawn incorrectly with Freetype versions > 2.1.3.
|
||||
// A simple workaround is to disable outline fonts for it and use
|
||||
// automatic outline drawing.
|
||||
const int outline_font = get_font_outline(i);
|
||||
if (outline_font < 0)
|
||||
continue;
|
||||
const char *name = get_font_name(i);
|
||||
const char *outline_name = get_font_name(outline_font);
|
||||
if ((ags_stricmp(name, "LucasFan-Font") == 0) &&
|
||||
(ags_stricmp(outline_name, "Arcade") == 0))
|
||||
set_font_outline(i, FONT_OUTLINE_AUTO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadLipsyncData() {
|
||||
std::unique_ptr<Stream> speechsync(_GP(AssetMgr)->OpenAsset("syncdata.dat", "voice"));
|
||||
if (!speechsync)
|
||||
return;
|
||||
// this game has voice lip sync
|
||||
int lipsync_fmt = speechsync->ReadInt32();
|
||||
if (lipsync_fmt != 4) {
|
||||
Debug::Printf(kDbgMsg_Info, "Unknown speech lip sync format (%d).\nLip sync disabled.", lipsync_fmt);
|
||||
} else {
|
||||
_G(numLipLines) = speechsync->ReadInt32();
|
||||
_GP(splipsync).resize(_G(numLipLines));
|
||||
for (int ee = 0; ee < _G(numLipLines); ee++) {
|
||||
_GP(splipsync)[ee].numPhonemes = speechsync->ReadInt16();
|
||||
speechsync->Read(_GP(splipsync)[ee].filename, 14);
|
||||
if (_GP(splipsync)[ee].numPhonemes == 0)
|
||||
continue;
|
||||
_GP(splipsync)[ee].endtimeoffs.resize(_GP(splipsync)[ee].numPhonemes);
|
||||
speechsync->ReadArrayOfInt32(&_GP(splipsync)[ee].endtimeoffs.front(), _GP(splipsync)[ee].numPhonemes);
|
||||
_GP(splipsync)[ee].frame.resize(_GP(splipsync)[ee].numPhonemes);
|
||||
speechsync->ReadArrayOfInt16(&_GP(splipsync)[ee].frame.front(), _GP(splipsync)[ee].numPhonemes);
|
||||
}
|
||||
}
|
||||
Debug::Printf(kDbgMsg_Info, "Lipsync data found and loaded");
|
||||
}
|
||||
|
||||
// Convert guis position and size to proper game resolution.
|
||||
// Necessary for pre 3.1.0 games only to sync with modern engine.
|
||||
static void ConvertGuiToGameRes(GameSetupStruct &game, GameDataVersion data_ver) {
|
||||
if (data_ver >= kGameVersion_310)
|
||||
return;
|
||||
|
||||
const int mul = game.GetDataUpscaleMult();
|
||||
for (int i = 0; i < game.numcursors; ++i) {
|
||||
game.mcurs[i].hotx *= mul;
|
||||
game.mcurs[i].hoty *= mul;
|
||||
}
|
||||
|
||||
for (int i = 0; i < game.numinvitems; ++i) {
|
||||
game.invinfo[i].hotx *= mul;
|
||||
game.invinfo[i].hoty *= mul;
|
||||
}
|
||||
|
||||
for (int i = 0; i < game.numgui; ++i) {
|
||||
GUIMain *cgp = &_GP(guis)[i];
|
||||
cgp->X *= mul;
|
||||
cgp->Y *= mul;
|
||||
if (cgp->Width < 1)
|
||||
cgp->Width = 1;
|
||||
if (cgp->Height < 1)
|
||||
cgp->Height = 1;
|
||||
// This is probably a way to fix GUIs meant to be covering whole screen
|
||||
if (cgp->Width == game.GetDataRes().Width - 1)
|
||||
cgp->Width = game.GetDataRes().Width;
|
||||
|
||||
cgp->Width *= mul;
|
||||
cgp->Height *= mul;
|
||||
|
||||
cgp->PopupAtMouseY *= mul;
|
||||
|
||||
for (int j = 0; j < cgp->GetControlCount(); ++j) {
|
||||
GUIObject *guio = cgp->GetControl(j);
|
||||
guio->X *= mul;
|
||||
guio->Y *= mul;
|
||||
Size sz = guio->GetSize() * mul;
|
||||
guio->SetSize(sz.Width, sz.Height);
|
||||
guio->IsActivated = false;
|
||||
guio->OnResized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert certain coordinates to data resolution (only if it's different from game resolution).
|
||||
// Necessary for 3.1.0 and above games with legacy "low-res coordinates" setting.
|
||||
static void ConvertObjectsToDataRes(GameSetupStruct &game, GameDataVersion data_ver) {
|
||||
if (data_ver < kGameVersion_310 || game.GetDataUpscaleMult() == 1)
|
||||
return;
|
||||
|
||||
const int mul = game.GetDataUpscaleMult();
|
||||
for (int i = 0; i < game.numcharacters; ++i) {
|
||||
game.chars[i].x /= mul;
|
||||
game.chars[i].y /= mul;
|
||||
}
|
||||
|
||||
for (auto &inv : _GP(guiinv)) {
|
||||
inv.ItemWidth /= mul;
|
||||
inv.ItemHeight /= mul;
|
||||
inv.OnResized();
|
||||
}
|
||||
}
|
||||
|
||||
void InitGameResolution(GameSetupStruct &game, GameDataVersion data_ver) {
|
||||
Debug::Printf("Initializing resolution settings");
|
||||
const Size game_size = game.GetGameRes();
|
||||
_GP(usetup).textheight = get_font_height_outlined(0) + 1;
|
||||
|
||||
Debug::Printf(kDbgMsg_Info, "Game native resolution: %d x %d (%d bit)%s", game_size.Width, game_size.Height, game.color_depth * 8,
|
||||
game.IsLegacyLetterbox() ? " letterbox-by-design" : "");
|
||||
|
||||
// Backwards compatible resolution conversions
|
||||
ConvertGuiToGameRes(game, data_ver);
|
||||
ConvertObjectsToDataRes(game, data_ver);
|
||||
|
||||
// Assign general game viewports
|
||||
Rect viewport = RectWH(game_size);
|
||||
_GP(play).SetMainViewport(viewport);
|
||||
_GP(play).SetUIViewport(viewport);
|
||||
|
||||
// Assign ScriptSystem's resolution variables
|
||||
_GP(scsystem).width = game.GetGameRes().Width;
|
||||
_GP(scsystem).height = game.GetGameRes().Height;
|
||||
_GP(scsystem).coldepth = game.GetColorDepth();
|
||||
_GP(scsystem).viewport_width = game_to_data_coord(_GP(play).GetMainViewport().GetWidth());
|
||||
_GP(scsystem).viewport_height = game_to_data_coord(_GP(play).GetMainViewport().GetHeight());
|
||||
}
|
||||
|
||||
HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver) {
|
||||
GameSetupStruct &game = ents.Game;
|
||||
const ScriptAPIVersion base_api = (ScriptAPIVersion)game.options[OPT_BASESCRIPTAPI];
|
||||
const ScriptAPIVersion compat_api = (ScriptAPIVersion)game.options[OPT_SCRIPTCOMPATLEV];
|
||||
if (data_ver >= kGameVersion_341) {
|
||||
const char *base_api_name = GetScriptAPIName(base_api);
|
||||
const char *compat_api_name = GetScriptAPIName(compat_api);
|
||||
Debug::Printf(kDbgMsg_Info, "Requested script API: %s (%d), compat level: %s (%d)",
|
||||
base_api >= 0 && base_api <= kScriptAPI_Current ? base_api_name : "unknown", base_api,
|
||||
compat_api >= 0 && compat_api <= kScriptAPI_Current ? compat_api_name : "unknown", compat_api);
|
||||
}
|
||||
// If the game was compiled using unsupported version of the script API,
|
||||
// we warn about potential incompatibilities but proceed further.
|
||||
if (game.options[OPT_BASESCRIPTAPI] > kScriptAPI_Current)
|
||||
_G(platform)->DisplayAlert("Warning: this game requests a higher version of AGS script API, it may not run correctly or run at all.");
|
||||
|
||||
//
|
||||
// 1. Check that the loaded data is valid and compatible with the current
|
||||
// engine capabilities.
|
||||
//
|
||||
if (game.numfonts == 0)
|
||||
return new GameInitError(kGameInitErr_NoFonts);
|
||||
if (game.audioClipTypes.size() > MAX_AUDIO_TYPES)
|
||||
return new GameInitError(kGameInitErr_TooManyAudioTypes,
|
||||
String::FromFormat("Required: %zu, max: %zu", game.audioClipTypes.size(), MAX_AUDIO_TYPES));
|
||||
|
||||
//
|
||||
// 3. Allocate and init game objects
|
||||
//
|
||||
_GP(charextra).resize(game.numcharacters);
|
||||
_GP(mls).resize(game.numcharacters + MAX_ROOM_OBJECTS + 1);
|
||||
init_game_drawdata();
|
||||
_GP(views) = std::move(ents.Views);
|
||||
|
||||
_GP(play).charProps.resize(game.numcharacters);
|
||||
_G(dialog) = std::move(ents.Dialogs);
|
||||
_G(old_dialog_scripts) = std::move(ents.OldDialogScripts);
|
||||
_G(old_speech_lines) = std::move(ents.OldSpeechLines);
|
||||
_G(old_dialog_scripts) = ents.OldDialogScripts;
|
||||
_G(old_speech_lines) = ents.OldSpeechLines;
|
||||
|
||||
// Set number of game channels corresponding to the loaded game version
|
||||
if (_G(loaded_game_file_version) < kGameVersion_360) {
|
||||
_GP(game).numGameChannels = MAX_GAME_CHANNELS_v320;
|
||||
_GP(game).numCompatGameChannels = TOTAL_AUDIO_CHANNELS_v320;
|
||||
} else {
|
||||
_GP(game).numGameChannels = MAX_GAME_CHANNELS;
|
||||
_GP(game).numCompatGameChannels = MAX_GAME_CHANNELS;
|
||||
}
|
||||
|
||||
HError err = InitAndRegisterGameEntities(game);
|
||||
if (!err)
|
||||
return new GameInitError(kGameInitErr_EntityInitFail, err);
|
||||
LoadFonts(game, data_ver);
|
||||
LoadLipsyncData();
|
||||
|
||||
//
|
||||
// 4. Initialize certain runtime variables
|
||||
//
|
||||
_G(game_paused) = 0; // reset the game paused flag
|
||||
_G(ifacepopped) = -1;
|
||||
|
||||
String svg_suffix;
|
||||
if (game.saveGameFileExtension[0] != 0)
|
||||
svg_suffix.Format(".%s", game.saveGameFileExtension);
|
||||
set_save_game_suffix(svg_suffix);
|
||||
|
||||
_GP(play).score_sound = game.scoreClipID;
|
||||
_GP(play).fade_effect = game.options[OPT_FADETYPE];
|
||||
|
||||
//
|
||||
// 5. Initialize runtime state of certain game objects
|
||||
//
|
||||
InitGameResolution(game, data_ver);
|
||||
for (auto &label : _GP(guilabels)) {
|
||||
// labels are not clickable by default
|
||||
label.SetClickable(false);
|
||||
}
|
||||
_GP(play).gui_draw_order.resize(game.numgui);
|
||||
for (int i = 0; i < game.numgui; ++i)
|
||||
_GP(play).gui_draw_order[i] = i;
|
||||
|
||||
update_gui_zorder();
|
||||
calculate_reserved_channel_count();
|
||||
// Default viewport and camera, draw data, etc, should be created when resolution is set
|
||||
_GP(play).CreatePrimaryViewportAndCamera();
|
||||
init_game_drawdata();
|
||||
|
||||
//
|
||||
// 6. Register engine API exports
|
||||
// NOTE: we must do this before plugin start, because some plugins may
|
||||
// require access to script API at initialization time.
|
||||
//
|
||||
ccSetScriptAliveTimer(1000 / 60u, 1000u, 150000u);
|
||||
setup_script_exports(base_api, compat_api);
|
||||
|
||||
//
|
||||
// 7. Start up plugins
|
||||
//
|
||||
pl_register_plugins(ents.PluginInfos);
|
||||
pl_startup_plugins();
|
||||
|
||||
//
|
||||
// 8. Create script modules
|
||||
// NOTE: we must do this after plugins, because some plugins may export
|
||||
// script symbols too.
|
||||
//
|
||||
if (!ents.GlobalScript)
|
||||
return new GameInitError(kGameInitErr_NoGlobalScript);
|
||||
_GP(gamescript) = ents.GlobalScript;
|
||||
_GP(dialogScriptsScript) = ents.DialogScript;
|
||||
_G(numScriptModules) = ents.ScriptModules.size();
|
||||
_GP(scriptModules) = ents.ScriptModules;
|
||||
AllocScriptModules();
|
||||
if (create_global_script())
|
||||
return new GameInitError(kGameInitErr_ScriptLinkFailed, cc_get_error().ErrorString);
|
||||
|
||||
return HGameInitError::None();
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
65
engines/ags/engine/game/game_init.h
Normal file
65
engines/ags/engine/game/game_init.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// This unit provides game initialization routine, which takes place after
|
||||
// main game file was successfully loaded.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_ENGINE_GAME_GAME_INIT_H
|
||||
#define AGS_ENGINE_GAME_GAME_INIT_H
|
||||
|
||||
#include "ags/shared/game/main_game_file.h"
|
||||
#include "ags/shared/util/string.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Engine {
|
||||
|
||||
using namespace Shared;
|
||||
|
||||
// Error codes for initializing the game
|
||||
enum GameInitErrorType {
|
||||
kGameInitErr_NoError,
|
||||
// currently AGS requires at least one font to be present in game
|
||||
kGameInitErr_NoFonts,
|
||||
kGameInitErr_TooManyAudioTypes,
|
||||
kGameInitErr_EntityInitFail,
|
||||
kGameInitErr_PluginNameInvalid,
|
||||
kGameInitErr_NoGlobalScript,
|
||||
kGameInitErr_ScriptLinkFailed
|
||||
};
|
||||
|
||||
String GetGameInitErrorText(GameInitErrorType err);
|
||||
|
||||
typedef TypedCodeError<GameInitErrorType, GetGameInitErrorText> GameInitError;
|
||||
typedef ErrorHandle<GameInitError> HGameInitError;
|
||||
|
||||
// Sets up game state for play using preloaded data
|
||||
HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver);
|
||||
|
||||
} // namespace Engine
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
843
engines/ags/engine/game/savegame.cpp
Normal file
843
engines/ags/engine/game/savegame.cpp
Normal file
@@ -0,0 +1,843 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/engine/ac/button.h"
|
||||
#include "ags/engine/ac/character.h"
|
||||
#include "ags/shared/ac/common.h"
|
||||
#include "ags/engine/ac/draw.h"
|
||||
#include "ags/engine/ac/dynamic_sprite.h"
|
||||
#include "ags/engine/ac/event.h"
|
||||
#include "ags/engine/ac/game.h"
|
||||
#include "ags/shared/ac/game_setup_struct.h"
|
||||
#include "ags/engine/ac/game_state.h"
|
||||
#include "ags/engine/ac/game_setup.h"
|
||||
#include "ags/engine/ac/global_audio.h"
|
||||
#include "ags/engine/ac/global_character.h"
|
||||
#include "ags/engine/ac/gui.h"
|
||||
#include "ags/engine/ac/mouse.h"
|
||||
#include "ags/engine/ac/overlay.h"
|
||||
#include "ags/engine/ac/region.h"
|
||||
#include "ags/engine/ac/rich_game_media.h"
|
||||
#include "ags/engine/ac/room.h"
|
||||
#include "ags/engine/ac/room_status.h"
|
||||
#include "ags/shared/ac/sprite_cache.h"
|
||||
#include "ags/engine/ac/system.h"
|
||||
#include "ags/engine/ac/timer.h"
|
||||
#include "ags/engine/ac/dynobj/dynobj_manager.h"
|
||||
#include "ags/engine/debugging/debugger.h"
|
||||
#include "ags/shared/debugging/out.h"
|
||||
#include "ags/engine/device/mouse_w32.h"
|
||||
#include "ags/shared/font/fonts.h"
|
||||
#include "ags/shared/gfx/bitmap.h"
|
||||
#include "ags/engine/gfx/ddb.h"
|
||||
#include "ags/engine/gfx/graphics_driver.h"
|
||||
#include "ags/engine/game/savegame.h"
|
||||
#include "ags/engine/game/savegame_components.h"
|
||||
#include "ags/engine/game/savegame_internal.h"
|
||||
#include "ags/engine/main/game_run.h"
|
||||
#include "ags/engine/main/engine.h"
|
||||
#include "ags/engine/main/main.h"
|
||||
#include "ags/engine/main/update.h"
|
||||
#include "ags/engine/platform/base/ags_platform_driver.h"
|
||||
#include "ags/engine/platform/base/sys_main.h"
|
||||
#include "ags/plugins/ags_plugin_evts.h"
|
||||
#include "ags/plugins/plugin_engine.h"
|
||||
#include "ags/engine/script/script.h"
|
||||
#include "ags/shared/script/cc_common.h"
|
||||
#include "ags/shared/util/data_stream.h"
|
||||
#include "ags/shared/util/file.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
#include "ags/shared/util/math.h"
|
||||
#include "ags/engine/media/audio/audio_system.h"
|
||||
#include "ags/globals.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace Shared;
|
||||
using namespace Engine;
|
||||
|
||||
// function is currently implemented in savegame_v321.cpp
|
||||
HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const PreservedParams &pp, RestoredData &r_data);
|
||||
|
||||
namespace AGS {
|
||||
namespace Engine {
|
||||
|
||||
const char *SavegameSource::LegacySignature = "Adventure Game Studio saved game";
|
||||
const char *SavegameSource::Signature = "Adventure Game Studio saved game v2";
|
||||
|
||||
SavegameSource::SavegameSource()
|
||||
: Version(kSvgVersion_Undefined) {
|
||||
}
|
||||
|
||||
SavegameDescription::SavegameDescription()
|
||||
: MainDataVersion(kGameVersion_Undefined)
|
||||
, ColorDepth(0)
|
||||
, LegacyID(0) {
|
||||
}
|
||||
|
||||
PreservedParams::PreservedParams()
|
||||
: SpeechVOX(0)
|
||||
, MusicVOX(0)
|
||||
, GlScDataSize(0) {
|
||||
}
|
||||
|
||||
RestoredData::ScriptData::ScriptData()
|
||||
: Len(0) {
|
||||
}
|
||||
|
||||
RestoredData::RestoredData()
|
||||
: FPS(0)
|
||||
, DoOnceCount(0u)
|
||||
, RoomVolume(kRoomVolumeNormal)
|
||||
, CursorID(0)
|
||||
, CursorMode(0) {
|
||||
memset(RoomLightLevels, 0, sizeof(RoomLightLevels));
|
||||
memset(RoomTintLevels, 0, sizeof(RoomTintLevels));
|
||||
memset(RoomZoomLevels1, 0, sizeof(RoomZoomLevels1));
|
||||
memset(RoomZoomLevels2, 0, sizeof(RoomZoomLevels2));
|
||||
memset(DoAmbient, 0, sizeof(DoAmbient));
|
||||
}
|
||||
|
||||
String GetSavegameErrorText(SavegameErrorType err) {
|
||||
switch (err) {
|
||||
case kSvgErr_NoError:
|
||||
return "No error.";
|
||||
case kSvgErr_FileOpenFailed:
|
||||
return "File not found or could not be opened.";
|
||||
case kSvgErr_SignatureFailed:
|
||||
return "Not an AGS saved game or unsupported format.";
|
||||
case kSvgErr_FormatVersionNotSupported:
|
||||
return "Save format version not supported.";
|
||||
case kSvgErr_IncompatibleEngine:
|
||||
return "Save was written by incompatible engine, or file is corrupted.";
|
||||
case kSvgErr_GameGuidMismatch:
|
||||
return "Game GUID does not match, saved by a different game.";
|
||||
case kSvgErr_ComponentListOpeningTagFormat:
|
||||
return "Failed to parse opening tag of the components list.";
|
||||
case kSvgErr_ComponentListClosingTagMissing:
|
||||
return "Closing tag of the components list was not met.";
|
||||
case kSvgErr_ComponentOpeningTagFormat:
|
||||
return "Failed to parse opening component tag.";
|
||||
case kSvgErr_ComponentClosingTagFormat:
|
||||
return "Failed to parse closing component tag.";
|
||||
case kSvgErr_ComponentSizeMismatch:
|
||||
return "Component data size mismatch.";
|
||||
case kSvgErr_UnsupportedComponent:
|
||||
return "Unknown and/or unsupported component.";
|
||||
case kSvgErr_ComponentSerialization:
|
||||
return "Failed to write the savegame component.";
|
||||
case kSvgErr_ComponentUnserialization:
|
||||
return "Failed to restore the savegame component.";
|
||||
case kSvgErr_InconsistentFormat:
|
||||
return "Inconsistent format, or file is corrupted.";
|
||||
case kSvgErr_UnsupportedComponentVersion:
|
||||
return "Component data version not supported.";
|
||||
case kSvgErr_GameContentAssertion:
|
||||
return "Saved content does not match current game.";
|
||||
case kSvgErr_InconsistentData:
|
||||
return "Inconsistent save data, or file is corrupted.";
|
||||
case kSvgErr_InconsistentPlugin:
|
||||
return "One of the game plugins did not restore its game data correctly.";
|
||||
case kSvgErr_DifferentColorDepth:
|
||||
return "Saved with the engine running at a different colour depth.";
|
||||
case kSvgErr_GameObjectInitFailed:
|
||||
return "Game object initialization failed after save restoration.";
|
||||
default:
|
||||
return "Unknown error.";
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap *RestoreSaveImage(Stream *in) {
|
||||
if (in->ReadInt32())
|
||||
return read_serialized_bitmap(in);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SkipSaveImage(Stream *in) {
|
||||
if (in->ReadInt32())
|
||||
skip_serialized_bitmap(in);
|
||||
}
|
||||
|
||||
HSaveError ReadDescription(Stream *in, SavegameVersion &svg_ver, SavegameDescription &desc, SavegameDescElem elems) {
|
||||
svg_ver = (SavegameVersion)in->ReadInt32();
|
||||
if (svg_ver < kSvgVersion_LowestSupported || svg_ver > kSvgVersion_Current)
|
||||
return new SavegameError(kSvgErr_FormatVersionNotSupported,
|
||||
String::FromFormat("Required: %d, supported: %d - %d.", svg_ver, kSvgVersion_LowestSupported, kSvgVersion_Current));
|
||||
|
||||
// Environment information
|
||||
if (svg_ver >= kSvgVersion_351)
|
||||
in->ReadInt32(); // environment info size
|
||||
if (elems & kSvgDesc_EnvInfo) {
|
||||
desc.EngineName = StrUtil::ReadString(in);
|
||||
desc.EngineVersion.SetFromString(StrUtil::ReadString(in));
|
||||
desc.GameGuid = StrUtil::ReadString(in);
|
||||
desc.GameTitle = StrUtil::ReadString(in);
|
||||
desc.MainDataFilename = StrUtil::ReadString(in);
|
||||
if (svg_ver >= kSvgVersion_Cmp_64bit)
|
||||
desc.MainDataVersion = (GameDataVersion)in->ReadInt32();
|
||||
desc.ColorDepth = in->ReadInt32();
|
||||
if (svg_ver >= kSvgVersion_351)
|
||||
desc.LegacyID = in->ReadInt32();
|
||||
} else {
|
||||
StrUtil::SkipString(in); // engine name
|
||||
StrUtil::SkipString(in); // engine version
|
||||
StrUtil::SkipString(in); // game guid
|
||||
StrUtil::SkipString(in); // game title
|
||||
StrUtil::SkipString(in); // main data filename
|
||||
if (svg_ver >= kSvgVersion_Cmp_64bit)
|
||||
in->ReadInt32(); // game data version
|
||||
in->ReadInt32(); // color depth
|
||||
if (svg_ver >= kSvgVersion_351)
|
||||
in->ReadInt32(); // game legacy id
|
||||
}
|
||||
// User description
|
||||
if (elems & kSvgDesc_UserText)
|
||||
desc.UserText = StrUtil::ReadString(in);
|
||||
else
|
||||
StrUtil::SkipString(in);
|
||||
if (elems & kSvgDesc_UserImage)
|
||||
desc.UserImage.reset(RestoreSaveImage(in));
|
||||
else
|
||||
SkipSaveImage(in);
|
||||
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
HSaveError ReadDescription_v321(Stream *in, SavegameVersion &svg_ver, SavegameDescription &desc, SavegameDescElem elems) {
|
||||
// Legacy savegame header
|
||||
if (elems & kSvgDesc_UserText)
|
||||
desc.UserText.Read(in);
|
||||
else
|
||||
StrUtil::SkipCStr(in);
|
||||
svg_ver = (SavegameVersion)in->ReadInt32();
|
||||
|
||||
// Check saved game format version
|
||||
if (svg_ver < kSvgVersion_LowestSupported ||
|
||||
svg_ver > kSvgVersion_Current) {
|
||||
return new SavegameError(kSvgErr_FormatVersionNotSupported,
|
||||
String::FromFormat("Required: %d, supported: %d - %d.", svg_ver, kSvgVersion_LowestSupported, kSvgVersion_Current));
|
||||
}
|
||||
|
||||
if (elems & kSvgDesc_UserImage)
|
||||
desc.UserImage.reset(RestoreSaveImage(in));
|
||||
else
|
||||
SkipSaveImage(in);
|
||||
|
||||
// This is the lowest legacy save format we support,
|
||||
// judging by the engine code received from CJ.
|
||||
const Version low_compat_version(3, 2, 0, 1103);
|
||||
String version_str = String::FromStream(in);
|
||||
Version eng_version(version_str);
|
||||
if (eng_version > _G(EngineVersion) || eng_version < low_compat_version) {
|
||||
// Engine version is either non-forward or non-backward compatible
|
||||
return new SavegameError(kSvgErr_IncompatibleEngine,
|
||||
String::FromFormat("Required: %s, supported: %s - %s.", eng_version.LongString.GetCStr(), low_compat_version.LongString.GetCStr(), _G(EngineVersion).LongString.GetCStr()));
|
||||
}
|
||||
if (elems & kSvgDesc_EnvInfo) {
|
||||
desc.MainDataFilename.Read(in);
|
||||
in->ReadInt32(); // unscaled game height with borders, now obsolete
|
||||
desc.ColorDepth = in->ReadInt32();
|
||||
} else {
|
||||
StrUtil::SkipCStr(in);
|
||||
in->ReadInt32(); // unscaled game height with borders, now obsolete
|
||||
in->ReadInt32(); // color depth
|
||||
}
|
||||
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
HSaveError OpenSavegameBase(const String &filename, SavegameSource *src, SavegameDescription *desc, SavegameDescElem elems) {
|
||||
UStream in(File::OpenFileRead(filename));
|
||||
if (!in.get())
|
||||
return new SavegameError(kSvgErr_FileOpenFailed, String::FromFormat("Requested filename: %s.", filename.GetCStr()));
|
||||
|
||||
// Skip MS Windows Vista rich media header
|
||||
RICH_GAME_MEDIA_HEADER rich_media_header;
|
||||
rich_media_header.ReadFromFile(in.get());
|
||||
|
||||
// Check saved game signature
|
||||
bool is_new_save = false;
|
||||
size_t pre_sig_pos = in->GetPosition();
|
||||
String svg_sig = String::FromStreamCount(in.get(), strlen(SavegameSource::Signature));
|
||||
if (svg_sig.Compare(SavegameSource::Signature) == 0) {
|
||||
is_new_save = true;
|
||||
} else {
|
||||
in->Seek(pre_sig_pos, kSeekBegin);
|
||||
svg_sig = String::FromStreamCount(in.get(), strlen(SavegameSource::LegacySignature));
|
||||
if (svg_sig.Compare(SavegameSource::LegacySignature) != 0)
|
||||
return new SavegameError(kSvgErr_SignatureFailed);
|
||||
}
|
||||
|
||||
SavegameVersion svg_ver;
|
||||
SavegameDescription temp_desc;
|
||||
HSaveError err;
|
||||
if (is_new_save)
|
||||
err = ReadDescription(in.get(), svg_ver, temp_desc, desc ? elems : kSvgDesc_None);
|
||||
else
|
||||
err = ReadDescription_v321(in.get(), svg_ver, temp_desc, desc ? elems : kSvgDesc_None);
|
||||
if (!err)
|
||||
return err;
|
||||
|
||||
if (src) {
|
||||
src->Filename = filename;
|
||||
src->Version = svg_ver;
|
||||
src->InputStream.reset(in.release()); // give the stream away to the caller
|
||||
}
|
||||
if (desc) {
|
||||
if (elems & kSvgDesc_EnvInfo) {
|
||||
desc->EngineName = temp_desc.EngineName;
|
||||
desc->EngineVersion = temp_desc.EngineVersion;
|
||||
desc->GameGuid = temp_desc.GameGuid;
|
||||
desc->LegacyID = temp_desc.LegacyID;
|
||||
desc->GameTitle = temp_desc.GameTitle;
|
||||
desc->MainDataFilename = temp_desc.MainDataFilename;
|
||||
desc->MainDataVersion = temp_desc.MainDataVersion;
|
||||
desc->ColorDepth = temp_desc.ColorDepth;
|
||||
}
|
||||
if (elems & kSvgDesc_UserText)
|
||||
desc->UserText = temp_desc.UserText;
|
||||
if (elems & kSvgDesc_UserImage)
|
||||
desc->UserImage.reset(temp_desc.UserImage.release());
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
HSaveError OpenSavegame(const String &filename, SavegameSource &src, SavegameDescription &desc, SavegameDescElem elems) {
|
||||
return OpenSavegameBase(filename, &src, &desc, elems);
|
||||
}
|
||||
|
||||
HSaveError OpenSavegame(const String &filename, SavegameDescription &desc, SavegameDescElem elems) {
|
||||
return OpenSavegameBase(filename, nullptr, &desc, elems);
|
||||
}
|
||||
|
||||
// Prepares engine for actual save restore (stops processes, cleans up memory)
|
||||
void DoBeforeRestore(PreservedParams &pp) {
|
||||
pp.SpeechVOX = _GP(play).voice_avail;
|
||||
pp.MusicVOX = _GP(play).separate_music_lib;
|
||||
memcpy(pp.GameOptions, _GP(game).options, GameSetupStruct::MAX_OPTIONS * sizeof(int));
|
||||
|
||||
unload_old_room();
|
||||
_G(raw_saved_screen).reset();
|
||||
remove_all_overlays();
|
||||
_GP(play).complete_overlay_on = 0;
|
||||
_GP(play).text_overlay_on = 0;
|
||||
|
||||
// cleanup dynamic sprites
|
||||
// NOTE: sprite 0 is a special constant sprite that cannot be dynamic
|
||||
for (int i = 1; i < (int)_GP(spriteset).GetSpriteSlotCount(); ++i) {
|
||||
if (_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC) {
|
||||
free_dynamic_sprite(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup drawn caches
|
||||
clear_drawobj_cache();
|
||||
|
||||
// preserve script data sizes and cleanup scripts
|
||||
pp.GlScDataSize = _G(gameinst)->globaldatasize;
|
||||
pp.ScMdDataSize.resize(_G(numScriptModules));
|
||||
for (size_t i = 0; i < _G(numScriptModules); ++i) {
|
||||
pp.ScMdDataSize[i] = _GP(moduleInst)[i]->globaldatasize;
|
||||
}
|
||||
|
||||
FreeAllScriptInstances();
|
||||
|
||||
// reset saved room states
|
||||
resetRoomStatuses();
|
||||
// reset temp room state
|
||||
_GP(troom) = RoomStatus();
|
||||
// reset (some of the?) GameState data
|
||||
// FIXME: investigate and refactor to be able to just reset whole object
|
||||
_GP(play).FreeProperties();
|
||||
_GP(play).FreeViewportsAndCameras();
|
||||
free_do_once_tokens();
|
||||
|
||||
RemoveAllButtonAnimations();
|
||||
// unregister gui controls from API exports
|
||||
// CHECKME: find out why are we doing this here? why only to gui controls?
|
||||
for (int i = 0; i < _GP(game).numgui; ++i) {
|
||||
unexport_gui_controls(i);
|
||||
}
|
||||
// Clear the managed object pool
|
||||
ccUnregisterAllObjects();
|
||||
|
||||
// NOTE: channels are array of MAX_SOUND_CHANNELS+1 size
|
||||
for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i) {
|
||||
stop_and_destroy_channel_ex(i, false);
|
||||
}
|
||||
|
||||
clear_music_cache();
|
||||
}
|
||||
|
||||
void RestoreViewportsAndCameras(const RestoredData &r_data) {
|
||||
// If restored from older saves, we have to adjust
|
||||
// cam and view sizes to a main viewport, which is init later
|
||||
const auto &main_view = _GP(play).GetMainViewport();
|
||||
|
||||
for (size_t i = 0; i < r_data.Cameras.size(); ++i) {
|
||||
const auto &cam_dat = r_data.Cameras[i];
|
||||
auto cam = _GP(play).GetRoomCamera(i);
|
||||
cam->SetID(cam_dat.ID);
|
||||
if ((cam_dat.Flags & kSvgCamPosLocked) != 0)
|
||||
cam->Lock();
|
||||
else
|
||||
cam->Release();
|
||||
// Set size first, or offset position may clamp to the room
|
||||
if (r_data.LegacyViewCamera)
|
||||
cam->SetSize(main_view.GetSize());
|
||||
else
|
||||
cam->SetSize(Size(cam_dat.Width, cam_dat.Height));
|
||||
cam->SetAt(cam_dat.Left, cam_dat.Top);
|
||||
}
|
||||
for (size_t i = 0; i < r_data.Viewports.size(); ++i) {
|
||||
const auto &view_dat = r_data.Viewports[i];
|
||||
auto view = _GP(play).GetRoomViewport(i);
|
||||
view->SetID(view_dat.ID);
|
||||
view->SetVisible((view_dat.Flags & kSvgViewportVisible) != 0);
|
||||
if (r_data.LegacyViewCamera)
|
||||
view->SetRect(RectWH(view_dat.Left, view_dat.Top, main_view.GetWidth(), main_view.GetHeight()));
|
||||
else
|
||||
view->SetRect(RectWH(view_dat.Left, view_dat.Top, view_dat.Width, view_dat.Height));
|
||||
view->SetZOrder(view_dat.ZOrder);
|
||||
// Restore camera link
|
||||
int cam_index = view_dat.CamID;
|
||||
if (cam_index < 0) continue;
|
||||
auto cam = _GP(play).GetRoomCamera(cam_index);
|
||||
view->LinkCamera(cam);
|
||||
cam->LinkToViewport(view);
|
||||
}
|
||||
_GP(play).InvalidateViewportZOrder();
|
||||
}
|
||||
|
||||
// Resets a number of options that are not supposed to be changed at runtime
|
||||
static void CopyPreservedGameOptions(GameSetupStructBase &gs, const PreservedParams &pp) {
|
||||
const auto restricted_opts = GameSetupStructBase::GetRestrictedOptions();
|
||||
for (auto opt : restricted_opts)
|
||||
gs.options[opt] = pp.GameOptions[opt];
|
||||
}
|
||||
|
||||
// Final processing after successfully restoring from save
|
||||
HSaveError DoAfterRestore(const PreservedParams &pp, RestoredData &r_data) {
|
||||
// Use a yellow dialog highlight for older game versions
|
||||
// CHECKME: it is dubious that this should be right here
|
||||
if (_G(loaded_game_file_version) < kGameVersion_331)
|
||||
_GP(play).dialog_options_highlight_color = DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT;
|
||||
|
||||
// Preserve whether the music vox is available
|
||||
_GP(play).voice_avail = pp.SpeechVOX;
|
||||
_GP(play).separate_music_lib = pp.MusicVOX;
|
||||
|
||||
// Restore particular game options that must not change at runtime
|
||||
CopyPreservedGameOptions(_GP(game), pp);
|
||||
|
||||
// Restore debug flags
|
||||
if (_G(debug_flags) & DBG_DEBUGMODE)
|
||||
_GP(play).debug_mode = 1;
|
||||
|
||||
// recache queued clips
|
||||
for (int i = 0; i < _GP(play).new_music_queue_size; ++i) {
|
||||
_GP(play).new_music_queue[i].cachedClip = nullptr;
|
||||
}
|
||||
|
||||
// Remap old sound nums in case we restored a save having a different list of audio clips
|
||||
RemapLegacySoundNums(_GP(game), _GP(views), _G(loaded_game_file_version));
|
||||
|
||||
// Restore Overlay bitmaps (older save format, which stored them along with overlays)
|
||||
auto &overs = get_overlays();
|
||||
for (auto &over_im : r_data.OverlayImages) {
|
||||
auto &over = overs[over_im._key];
|
||||
over.SetImage(std::move(over_im._value), over.HasAlphaChannel(), over.offsetX, over.offsetY);
|
||||
}
|
||||
|
||||
// Restore dynamic surfaces
|
||||
const size_t dynsurf_num = MIN((uint)MAX_DYNAMIC_SURFACES, r_data.DynamicSurfaces.size());
|
||||
for (size_t i = 0; i < dynsurf_num; ++i) {
|
||||
_G(dynamicallyCreatedSurfaces)[i] = std::move(r_data.DynamicSurfaces[i]);
|
||||
}
|
||||
|
||||
// Re-export any missing audio channel script objects, e.g. if restoring old save
|
||||
export_missing_audiochans();
|
||||
|
||||
// CHECKME: find out why are we doing this here? why only to gui controls?
|
||||
for (int i = 0; i < _GP(game).numgui; ++i)
|
||||
export_gui_controls(i);
|
||||
|
||||
update_gui_zorder();
|
||||
|
||||
AllocScriptModules();
|
||||
if (create_global_script()) {
|
||||
return new SavegameError(kSvgErr_GameObjectInitFailed,
|
||||
String::FromFormat("Unable to recreate global script: %s", cc_get_error().ErrorString.GetCStr()));
|
||||
}
|
||||
|
||||
// read the global data into the newly created script
|
||||
if (!r_data.GlobalScript.Data.empty())
|
||||
memcpy(_G(gameinst)->globaldata, &r_data.GlobalScript.Data.front(),
|
||||
MIN((size_t)_G(gameinst)->globaldatasize, r_data.GlobalScript.Len));
|
||||
|
||||
// restore the script module data
|
||||
for (size_t i = 0; i < _G(numScriptModules); ++i) {
|
||||
if (!r_data.ScriptModules[i].Data.empty())
|
||||
memcpy(_GP(moduleInst)[i]->globaldata, &r_data.ScriptModules[i].Data.front(),
|
||||
MIN((size_t)_GP(moduleInst)[i]->globaldatasize, r_data.ScriptModules[i].Len));
|
||||
}
|
||||
|
||||
setup_player_character(_GP(game).playercharacter);
|
||||
|
||||
// Save some parameters to restore them after room load
|
||||
int gstimer = _GP(play).gscript_timer;
|
||||
int oldx1 = _GP(play).mboundx1, oldx2 = _GP(play).mboundx2;
|
||||
int oldy1 = _GP(play).mboundy1, oldy2 = _GP(play).mboundy2;
|
||||
|
||||
// disable the queue momentarily
|
||||
int queuedMusicSize = _GP(play).music_queue_size;
|
||||
_GP(play).music_queue_size = 0;
|
||||
|
||||
// load the room the game was saved in
|
||||
if (_G(displayed_room) >= 0)
|
||||
load_new_room(_G(displayed_room), nullptr);
|
||||
else
|
||||
set_room_placeholder();
|
||||
|
||||
_GP(play).gscript_timer = gstimer;
|
||||
// restore the correct room volume (they might have modified
|
||||
// it with SetMusicVolume)
|
||||
_GP(thisroom).Options.MusicVolume = r_data.RoomVolume;
|
||||
|
||||
_GP(mouse).SetMoveLimit(Rect(oldx1, oldy1, oldx2, oldy2));
|
||||
|
||||
set_cursor_mode(r_data.CursorMode);
|
||||
set_mouse_cursor(r_data.CursorID, true);
|
||||
if (r_data.CursorMode == MODE_USE)
|
||||
SetActiveInventory(_G(playerchar)->activeinv);
|
||||
// ensure that the current cursor is locked
|
||||
_GP(spriteset).PrecacheSprite(_GP(game).mcurs[r_data.CursorID].pic);
|
||||
|
||||
sys_window_set_title(_GP(play).game_name.GetCStr());
|
||||
|
||||
if (_G(displayed_room) >= 0) {
|
||||
// Fixup the frame index, in case the restored room does not have enough background frames
|
||||
if (_GP(play).bg_frame < 0 || static_cast<size_t>(_GP(play).bg_frame) >= _GP(thisroom).BgFrameCount)
|
||||
_GP(play).bg_frame = 0;
|
||||
|
||||
for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) {
|
||||
if (r_data.RoomBkgScene[i]) {
|
||||
_GP(thisroom).BgFrames[i].Graphic = r_data.RoomBkgScene[i];
|
||||
}
|
||||
}
|
||||
|
||||
_G(in_new_room) = 3; // don't run "enters screen" events
|
||||
// now that room has loaded, copy saved light levels in
|
||||
for (size_t i = 0; i < MAX_ROOM_REGIONS; ++i) {
|
||||
_GP(thisroom).Regions[i].Light = r_data.RoomLightLevels[i];
|
||||
_GP(thisroom).Regions[i].Tint = r_data.RoomTintLevels[i];
|
||||
}
|
||||
generate_light_table();
|
||||
|
||||
for (size_t i = 0; i < MAX_WALK_AREAS; ++i) {
|
||||
_GP(thisroom).WalkAreas[i].ScalingFar = r_data.RoomZoomLevels1[i];
|
||||
_GP(thisroom).WalkAreas[i].ScalingNear = r_data.RoomZoomLevels2[i];
|
||||
}
|
||||
|
||||
on_background_frame_change();
|
||||
}
|
||||
|
||||
GUI::Options.DisabledStyle = static_cast<GuiDisableStyle>(_GP(game).options[OPT_DISABLEOFF]);
|
||||
|
||||
// restore the queue now that the music is playing
|
||||
_GP(play).music_queue_size = queuedMusicSize;
|
||||
|
||||
if (_GP(play).digital_master_volume >= 0) {
|
||||
int temp_vol = _GP(play).digital_master_volume;
|
||||
_GP(play).digital_master_volume = -1; // reset to invalid state before re-applying
|
||||
System_SetVolume(temp_vol);
|
||||
}
|
||||
|
||||
// Run audio clips on channels
|
||||
// these two crossfading parameters have to be temporarily reset
|
||||
const int cf_in_chan = _GP(play).crossfading_in_channel;
|
||||
const int cf_out_chan = _GP(play).crossfading_out_channel;
|
||||
_GP(play).crossfading_in_channel = 0;
|
||||
_GP(play).crossfading_out_channel = 0;
|
||||
|
||||
// NOTE: channels are array of MAX_SOUND_CHANNELS+1 size
|
||||
for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i) {
|
||||
const RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i];
|
||||
if (chan_info.ClipID < 0)
|
||||
continue;
|
||||
if ((size_t)chan_info.ClipID >= _GP(game).audioClips.size()) {
|
||||
return new SavegameError(kSvgErr_GameObjectInitFailed,
|
||||
String::FromFormat("Invalid audio clip index: %d (clip count: %zu).", chan_info.ClipID, _GP(game).audioClips.size()));
|
||||
}
|
||||
play_audio_clip_on_channel(i, &_GP(game).audioClips[chan_info.ClipID],
|
||||
chan_info.Priority, chan_info.Repeat, chan_info.Pos);
|
||||
|
||||
auto *ch = AudioChans::GetChannel(i);
|
||||
if (ch != nullptr) {
|
||||
ch->set_volume_direct(chan_info.VolAsPercent, chan_info.Vol);
|
||||
ch->set_speed(chan_info.Speed);
|
||||
ch->set_panning(chan_info.Pan);
|
||||
ch->_xSource = chan_info.XSource;
|
||||
ch->_ySource = chan_info.YSource;
|
||||
ch->_maximumPossibleDistanceAway = chan_info.MaxDist;
|
||||
|
||||
if ((chan_info.Flags & kSvgAudioPaused) != 0)
|
||||
ch->pause();
|
||||
}
|
||||
}
|
||||
if ((cf_in_chan > 0) && (AudioChans::GetChannel(cf_in_chan) != nullptr))
|
||||
_GP(play).crossfading_in_channel = cf_in_chan;
|
||||
if ((cf_out_chan > 0) && (AudioChans::GetChannel(cf_out_chan) != nullptr))
|
||||
_GP(play).crossfading_out_channel = cf_out_chan;
|
||||
|
||||
// If there were synced audio tracks, the time taken to load in the
|
||||
// different channels will have thrown them out of sync, so re-time it
|
||||
// NOTE: channels are array of MAX_SOUND_CHANNELS+1 size
|
||||
for (int i = 0; i < TOTAL_AUDIO_CHANNELS; ++i) {
|
||||
auto *ch = AudioChans::GetChannelIfPlaying(i);
|
||||
int pos = r_data.AudioChans[i].Pos;
|
||||
if ((pos > 0) && (ch != nullptr)) {
|
||||
ch->seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = NUM_SPEECH_CHANS; i < _GP(game).numGameChannels; ++i) {
|
||||
if (r_data.DoAmbient[i])
|
||||
PlayAmbientSound(i, r_data.DoAmbient[i], _GP(ambient)[i].vol, _GP(ambient)[i].x, _GP(ambient)[i].y);
|
||||
}
|
||||
update_directional_sound_vol();
|
||||
|
||||
adjust_fonts_for_render_mode(_GP(game).options[OPT_ANTIALIASFONTS] != 0);
|
||||
|
||||
restore_characters();
|
||||
restore_overlays();
|
||||
restore_movelists();
|
||||
|
||||
GUI::MarkAllGUIForUpdate(true, true);
|
||||
|
||||
RestoreViewportsAndCameras(r_data);
|
||||
|
||||
_GP(play).ClearIgnoreInput(); // don't keep ignored input after save restore
|
||||
update_polled_stuff();
|
||||
|
||||
pl_run_plugin_hooks(AGSE_POSTRESTOREGAME, 0);
|
||||
|
||||
if (_G(displayed_room) < 0) {
|
||||
// the restart point, no room was loaded
|
||||
load_new_room(_G(playerchar)->room, _G(playerchar));
|
||||
|
||||
first_room_initialization();
|
||||
}
|
||||
|
||||
if ((_GP(play).music_queue_size > 0) && (_G(cachedQueuedMusic) == nullptr)) {
|
||||
_G(cachedQueuedMusic) = load_music_from_disk(_GP(play).music_queue[0], 0);
|
||||
}
|
||||
|
||||
// Test if the old-style audio had playing music and it was properly loaded
|
||||
if (_G(current_music_type) > 0) {
|
||||
if ((_G(crossFading) > 0 && !AudioChans::GetChannelIfPlaying(_G(crossFading))) ||
|
||||
(_G(crossFading) <= 0 && !AudioChans::GetChannelIfPlaying(SCHAN_MUSIC))) {
|
||||
_G(current_music_type) = 0; // playback failed, reset flag
|
||||
}
|
||||
}
|
||||
|
||||
set_game_speed(r_data.FPS);
|
||||
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
HSaveError RestoreGameState(Stream *in, SavegameVersion svg_version) {
|
||||
PreservedParams pp;
|
||||
RestoredData r_data;
|
||||
DoBeforeRestore(pp);
|
||||
HSaveError err;
|
||||
if (svg_version >= kSvgVersion_Components) {
|
||||
err = SavegameComponents::ReadAll(in, svg_version, pp, r_data);
|
||||
} else {
|
||||
GameDataVersion use_dataver = _GP(usetup).legacysave_assume_dataver != kGameVersion_Undefined ? _GP(usetup).legacysave_assume_dataver
|
||||
: _G(loaded_game_file_version);
|
||||
err = restore_save_data_v321(in, use_dataver, pp, r_data);
|
||||
}
|
||||
if (!err)
|
||||
return err;
|
||||
return DoAfterRestore(pp, r_data);
|
||||
}
|
||||
|
||||
|
||||
void WriteSaveImage(Stream *out, const Bitmap *screenshot) {
|
||||
// store the screenshot at the start to make it easily accessible
|
||||
out->WriteInt32((screenshot == nullptr) ? 0 : 1);
|
||||
|
||||
if (screenshot)
|
||||
serialize_bitmap(screenshot, out);
|
||||
}
|
||||
|
||||
void WriteDescription(Stream *out, const String &user_text, const Bitmap *user_image) {
|
||||
// Data format version
|
||||
out->WriteInt32(kSvgVersion_Current);
|
||||
soff_t env_pos = out->GetPosition();
|
||||
out->WriteInt32(0);
|
||||
// Environment information
|
||||
StrUtil::WriteString(get_engine_name(), out);
|
||||
StrUtil::WriteString(_G(EngineVersion).LongString, out);
|
||||
StrUtil::WriteString(_GP(game).guid, out);
|
||||
StrUtil::WriteString(_GP(game).gamename, out);
|
||||
StrUtil::WriteString(_GP(ResPaths).GamePak.Name, out);
|
||||
out->WriteInt32(_G(loaded_game_file_version));
|
||||
out->WriteInt32(_GP(game).GetColorDepth());
|
||||
out->WriteInt32(_GP(game).uniqueid);
|
||||
soff_t env_end_pos = out->GetPosition();
|
||||
out->Seek(env_pos, kSeekBegin);
|
||||
out->WriteInt32(env_end_pos - env_pos);
|
||||
out->Seek(env_end_pos, kSeekBegin);
|
||||
// User description
|
||||
StrUtil::WriteString(user_text, out);
|
||||
WriteSaveImage(out, user_image);
|
||||
}
|
||||
|
||||
Stream *StartSavegame(const String &filename, const String &user_text, const Bitmap *user_image) {
|
||||
Stream *out = Shared::File::CreateFile(filename);
|
||||
if (!out)
|
||||
return nullptr;
|
||||
|
||||
// Initialize and write Vista header
|
||||
RICH_GAME_MEDIA_HEADER vistaHeader;
|
||||
memset(&vistaHeader, 0, sizeof(RICH_GAME_MEDIA_HEADER));
|
||||
vistaHeader.dwMagicNumber = RM_MAGICNUMBER;
|
||||
vistaHeader.dwHeaderVersion = 1;
|
||||
vistaHeader.dwHeaderSize = sizeof(RICH_GAME_MEDIA_HEADER);
|
||||
vistaHeader.dwThumbnailOffsetHigherDword = 0;
|
||||
vistaHeader.dwThumbnailOffsetLowerDword = 0;
|
||||
vistaHeader.dwThumbnailSize = 0;
|
||||
convert_guid_from_text_to_binary(_GP(game).guid, &vistaHeader.guidGameId[0]);
|
||||
|
||||
vistaHeader.setSaveName(user_text);
|
||||
|
||||
vistaHeader.szLevelName[0] = 0;
|
||||
vistaHeader.szComments[0] = 0;
|
||||
// MS Windows Vista rich media header
|
||||
vistaHeader.WriteToFile(out);
|
||||
|
||||
// Savegame signature
|
||||
out->Write(SavegameSource::Signature, strlen(SavegameSource::Signature));
|
||||
|
||||
// CHECKME: what is this plugin hook suppose to mean, and if it is called here correctly
|
||||
pl_run_plugin_hooks(AGSE_PRESAVEGAME, 0);
|
||||
|
||||
// Write descrition block
|
||||
WriteDescription(out, user_text, user_image);
|
||||
return out;
|
||||
}
|
||||
|
||||
void DoBeforeSave() {
|
||||
if (_GP(play).cur_music_number >= 0) {
|
||||
if (IsMusicPlaying() == 0)
|
||||
_GP(play).cur_music_number = -1;
|
||||
}
|
||||
|
||||
if (_G(displayed_room) >= 0) {
|
||||
// update the current room script's data segment copy
|
||||
if (_G(roominst))
|
||||
save_room_data_segment();
|
||||
|
||||
// Update the saved interaction variable values
|
||||
for (size_t i = 0; i < _GP(thisroom).LocalVariables.size() && i < (size_t)MAX_GLOBAL_VARIABLES; ++i)
|
||||
_G(croom)->interactionVariableValues[i] = _GP(thisroom).LocalVariables[i].Value;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveGameState(Stream *out) {
|
||||
DoBeforeSave();
|
||||
SavegameComponents::WriteAllCommon(out);
|
||||
}
|
||||
|
||||
void ReadPluginSaveData(Stream *in, PluginSvgVersion svg_ver, soff_t max_size) {
|
||||
const soff_t start_pos = in->GetPosition();
|
||||
const soff_t end_pos = start_pos + max_size;
|
||||
|
||||
if (svg_ver >= kPluginSvgVersion_36115) {
|
||||
uint32_t num_plugins_read = in->ReadInt32();
|
||||
soff_t cur_pos = start_pos;
|
||||
while ((num_plugins_read--) > 0 && (cur_pos < end_pos)) {
|
||||
String pl_name = StrUtil::ReadString(in);
|
||||
size_t data_size = in->ReadInt32();
|
||||
soff_t data_start = in->GetPosition();
|
||||
|
||||
auto pl_handle = AGSE_RESTOREGAME;
|
||||
pl_set_file_handle(pl_handle, in);
|
||||
pl_run_plugin_hook_by_name(pl_name, AGSE_RESTOREGAME, pl_handle);
|
||||
pl_clear_file_handle();
|
||||
|
||||
// Seek to the end of plugin data, in case it ended up reading not in the end
|
||||
cur_pos = data_start + data_size;
|
||||
in->Seek(cur_pos, kSeekBegin);
|
||||
}
|
||||
} else {
|
||||
String pl_name;
|
||||
for (uint32_t pl_index = 0; pl_query_next_plugin_for_event(AGSE_RESTOREGAME, pl_index, pl_name); ++pl_index) {
|
||||
auto pl_handle = AGSE_RESTOREGAME;
|
||||
pl_set_file_handle(pl_handle, in);
|
||||
pl_run_plugin_hook_by_index(pl_index, AGSE_RESTOREGAME, pl_handle);
|
||||
pl_clear_file_handle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WritePluginSaveData(Stream *out) {
|
||||
soff_t pluginnum_pos = out->GetPosition();
|
||||
out->WriteInt32(0); // number of plugins which wrote data
|
||||
|
||||
uint32_t num_plugins_wrote = 0;
|
||||
String pl_name;
|
||||
for (uint32_t pl_index = 0; pl_query_next_plugin_for_event(AGSE_SAVEGAME, pl_index, pl_name); ++pl_index) {
|
||||
// NOTE: we don't care if they really write anything,
|
||||
// but count them so long as they subscribed to AGSE_SAVEGAME
|
||||
num_plugins_wrote++;
|
||||
|
||||
// Write a header for plugin data
|
||||
StrUtil::WriteString(pl_name, out);
|
||||
soff_t data_size_pos = out->GetPosition();
|
||||
out->WriteInt32(0); // data size
|
||||
|
||||
// Create a stream section and write plugin data
|
||||
soff_t data_start_pos = out->GetPosition();
|
||||
auto pl_handle = AGSE_SAVEGAME;
|
||||
pl_set_file_handle(pl_handle, out);
|
||||
pl_run_plugin_hook_by_index(pl_index, AGSE_SAVEGAME, pl_handle);
|
||||
pl_clear_file_handle();
|
||||
|
||||
// Finalize header
|
||||
soff_t data_end_pos = out->GetPosition();
|
||||
out->Seek(data_size_pos, kSeekBegin);
|
||||
out->WriteInt32(data_end_pos - data_start_pos);
|
||||
out->Seek(0, kSeekEnd);
|
||||
}
|
||||
|
||||
// Write number of plugins
|
||||
out->Seek(pluginnum_pos, kSeekBegin);
|
||||
out->WriteInt32(num_plugins_wrote);
|
||||
out->Seek(0, kSeekEnd);
|
||||
}
|
||||
|
||||
} // namespace Engine
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
180
engines/ags/engine/game/savegame.h
Normal file
180
engines/ags/engine/game/savegame.h
Normal file
@@ -0,0 +1,180 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_ENGINE_GAME_SAVEGAME_H
|
||||
#define AGS_ENGINE_GAME_SAVEGAME_H
|
||||
|
||||
#include "common/std/memory.h"
|
||||
#include "ags/shared/core/platform.h"
|
||||
#include "ags/shared/ac/game_version.h"
|
||||
#include "ags/shared/util/error.h"
|
||||
#include "ags/shared/util/version.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
|
||||
namespace Shared {
|
||||
class Bitmap;
|
||||
class Stream;
|
||||
} // namespace Shared
|
||||
|
||||
namespace Engine {
|
||||
|
||||
using Shared::Bitmap;
|
||||
using Shared::ErrorHandle;
|
||||
using Shared::TypedCodeError;
|
||||
using Shared::Stream;
|
||||
using Shared::String;
|
||||
using Shared::Version;
|
||||
|
||||
typedef std::shared_ptr<Stream> PStream;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Savegame version history
|
||||
//
|
||||
// 8 last old style saved game format (of AGS 3.2.1)
|
||||
// 9 first new style (self-descriptive block-based) format version
|
||||
// Since 3.6.0: value is defined as AGS version represented as NN,NN,NN,NN.
|
||||
//-----------------------------------------------------------------------------
|
||||
enum SavegameVersion {
|
||||
kSvgVersion_Undefined = 0,
|
||||
kSvgVersion_321 = 8,
|
||||
kSvgVersion_Components = 9,
|
||||
kSvgVersion_Cmp_64bit = 10,
|
||||
kSvgVersion_350_final = 11,
|
||||
kSvgVersion_350_final2 = 12,
|
||||
kSvgVersion_351 = 13,
|
||||
kSvgVersion_360_beta = 3060023,
|
||||
kSvgVersion_360_final = 3060041,
|
||||
kSvgVersion_361 = 3060115,
|
||||
kSvgVersion_361_p8 = 3060130,
|
||||
kSvgVersion_Current = kSvgVersion_361_p8,
|
||||
kSvgVersion_LowestSupported = kSvgVersion_321 // change if support dropped
|
||||
};
|
||||
|
||||
// Error codes for save restoration routine
|
||||
enum SavegameErrorType {
|
||||
kSvgErr_NoError,
|
||||
kSvgErr_FileOpenFailed,
|
||||
kSvgErr_SignatureFailed,
|
||||
kSvgErr_FormatVersionNotSupported,
|
||||
kSvgErr_IncompatibleEngine,
|
||||
kSvgErr_GameGuidMismatch,
|
||||
kSvgErr_ComponentListOpeningTagFormat,
|
||||
kSvgErr_ComponentListClosingTagMissing,
|
||||
kSvgErr_ComponentOpeningTagFormat,
|
||||
kSvgErr_ComponentClosingTagFormat,
|
||||
kSvgErr_ComponentSizeMismatch,
|
||||
kSvgErr_UnsupportedComponent,
|
||||
kSvgErr_ComponentSerialization,
|
||||
kSvgErr_ComponentUnserialization,
|
||||
kSvgErr_InconsistentFormat,
|
||||
kSvgErr_UnsupportedComponentVersion,
|
||||
kSvgErr_GameContentAssertion,
|
||||
kSvgErr_InconsistentData,
|
||||
kSvgErr_InconsistentPlugin,
|
||||
kSvgErr_DifferentColorDepth,
|
||||
kSvgErr_GameObjectInitFailed,
|
||||
kNumSavegameError
|
||||
};
|
||||
|
||||
String GetSavegameErrorText(SavegameErrorType err);
|
||||
|
||||
typedef TypedCodeError<SavegameErrorType, GetSavegameErrorText> SavegameError;
|
||||
typedef ErrorHandle<SavegameError> HSaveError;
|
||||
|
||||
// SavegameSource defines a successfully opened savegame stream
|
||||
struct SavegameSource {
|
||||
// Signature of the current savegame format
|
||||
static const char *Signature;
|
||||
// Signature of the legacy savegame format
|
||||
static const char *LegacySignature;
|
||||
|
||||
// Name of the savefile
|
||||
String Filename;
|
||||
// Savegame format version
|
||||
SavegameVersion Version;
|
||||
// A ponter to the opened stream
|
||||
std::unique_ptr<Stream> InputStream;
|
||||
|
||||
SavegameSource();
|
||||
};
|
||||
|
||||
// Supported elements of savegame description;
|
||||
// these may be used as flags to define valid fields
|
||||
enum SavegameDescElem {
|
||||
kSvgDesc_None = 0,
|
||||
kSvgDesc_EnvInfo = 0x0001,
|
||||
kSvgDesc_UserText = 0x0002,
|
||||
kSvgDesc_UserImage = 0x0004,
|
||||
kSvgDesc_All = kSvgDesc_EnvInfo | kSvgDesc_UserText | kSvgDesc_UserImage
|
||||
};
|
||||
|
||||
// SavegameDescription describes savegame with information about the environment
|
||||
// it was created in, and custom data provided by user
|
||||
struct SavegameDescription {
|
||||
// Name of the engine that saved the game
|
||||
String EngineName;
|
||||
// Version of the engine that saved the game
|
||||
Version EngineVersion;
|
||||
// Guid of the game which made this save
|
||||
String GameGuid;
|
||||
// Legacy uniqueid of the game, for use in older games with no GUID
|
||||
int LegacyID;
|
||||
// Title of the game which made this save
|
||||
String GameTitle;
|
||||
// Name of the main data file used; this is needed to properly
|
||||
// load saves made by "minigames"
|
||||
String MainDataFilename;
|
||||
// Game's main data version; should be checked early to know
|
||||
// if the save was made for the supported game format
|
||||
GameDataVersion MainDataVersion;
|
||||
// Native color depth of the game; this is required to
|
||||
// properly restore dynamic graphics from the save
|
||||
int ColorDepth;
|
||||
|
||||
String UserText;
|
||||
std::unique_ptr<Bitmap> UserImage;
|
||||
|
||||
SavegameDescription();
|
||||
};
|
||||
|
||||
|
||||
// Opens savegame for reading; optionally reads description, if any is provided
|
||||
HSaveError OpenSavegame(const String &filename, SavegameSource &src,
|
||||
SavegameDescription &desc, SavegameDescElem elems = kSvgDesc_All);
|
||||
// Opens savegame and reads the savegame description
|
||||
HSaveError OpenSavegame(const String &filename, SavegameDescription &desc, SavegameDescElem elems = kSvgDesc_All);
|
||||
|
||||
// Reads the game data from the save stream and reinitializes game state
|
||||
HSaveError RestoreGameState(Stream *in, SavegameVersion svg_version);
|
||||
|
||||
// Opens savegame for writing and puts in savegame description
|
||||
Stream *StartSavegame(const String &filename, const String &user_text, const Bitmap *user_image);
|
||||
|
||||
// Prepares game for saving state and writes game data into the save stream
|
||||
void SaveGameState(Stream *out);
|
||||
|
||||
} // namespace Engine
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
1348
engines/ags/engine/game/savegame_components.cpp
Normal file
1348
engines/ags/engine/game/savegame_components.cpp
Normal file
File diff suppressed because it is too large
Load Diff
68
engines/ags/engine/game/savegame_components.h
Normal file
68
engines/ags/engine/game/savegame_components.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_ENGINE_GAME_SAVEGAME_COMPONENTS_H
|
||||
#define AGS_ENGINE_GAME_SAVEGAME_COMPONENTS_H
|
||||
|
||||
#include "ags/engine/game/savegame.h"
|
||||
#include "ags/shared/util/stream.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
|
||||
namespace Shared {
|
||||
struct Interaction;
|
||||
} // namespace Shared
|
||||
|
||||
namespace Engine {
|
||||
|
||||
using Shared::Stream;
|
||||
using Shared::Interaction;
|
||||
|
||||
struct PreservedParams;
|
||||
struct RestoredData;
|
||||
|
||||
namespace SavegameComponents {
|
||||
|
||||
extern void component_handlers_init();
|
||||
extern void component_handlers_free();
|
||||
|
||||
// Reads all available components from the stream
|
||||
HSaveError ReadAll(Stream *in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data);
|
||||
// Writes a full list of common components to the stream
|
||||
HSaveError WriteAllCommon(Stream *out);
|
||||
|
||||
// Utility functions for reading and writing legacy interactions,
|
||||
// or their "times run" counters separately.
|
||||
void ReadTimesRun272(Interaction &intr, Stream *in);
|
||||
HSaveError ReadInteraction272(Interaction &intr, Stream *in);
|
||||
void WriteTimesRun272(const Interaction &intr, Stream *out);
|
||||
void WriteInteraction272(const Interaction &intr, Stream *out);
|
||||
|
||||
// Precreates primary camera and viewport and reads legacy camera data
|
||||
void ReadLegacyCameraState(Stream *in, RestoredData &r_data);
|
||||
} // namespace SavegameComponents
|
||||
|
||||
} // namespace Engine
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
168
engines/ags/engine/game/savegame_internal.h
Normal file
168
engines/ags/engine/game/savegame_internal.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef AGS_ENGINE_GAME_SAVEGAME_INTERNAL_H
|
||||
#define AGS_ENGINE_GAME_SAVEGAME_INTERNAL_H
|
||||
|
||||
#include "common/std/memory.h"
|
||||
#include "common/std/vector.h"
|
||||
#include "common/std/map.h"
|
||||
#include "ags/shared/ac/common_defines.h"
|
||||
#include "ags/shared/game/room_struct.h"
|
||||
#include "ags/shared/gfx/bitmap.h"
|
||||
#include "ags/engine/media/audio/audio_defines.h"
|
||||
|
||||
namespace AGS3 {
|
||||
namespace AGS {
|
||||
namespace Engine {
|
||||
|
||||
using AGS::Shared::Bitmap;
|
||||
using AGS::Shared::Stream;
|
||||
|
||||
typedef std::shared_ptr<Bitmap> PBitmap;
|
||||
|
||||
// PreservedParams keeps old values of particular gameplay
|
||||
// parameters that are saved before the save restoration
|
||||
// and either applied or compared to new values after
|
||||
// loading save data
|
||||
struct PreservedParams {
|
||||
// Whether speech and audio packages available
|
||||
bool SpeechVOX = false;
|
||||
bool MusicVOX = false;
|
||||
// Game options, to preserve ones that must not change at runtime
|
||||
int GameOptions[GameSetupStructBase::MAX_OPTIONS]{};
|
||||
// Script global data sizes
|
||||
size_t GlScDataSize = 0u;
|
||||
std::vector<size_t> ScMdDataSize;
|
||||
|
||||
PreservedParams();
|
||||
};
|
||||
|
||||
// Audio playback state flags, used only in serialization
|
||||
enum AudioSvgPlaybackFlags {
|
||||
kSvgAudioPaused = 0x01
|
||||
};
|
||||
|
||||
enum GameViewCamFlags {
|
||||
kSvgGameAutoRoomView = 0x01
|
||||
};
|
||||
|
||||
enum CameraSaveFlags {
|
||||
kSvgCamPosLocked = 0x01
|
||||
};
|
||||
|
||||
enum ViewportSaveFlags {
|
||||
kSvgViewportVisible = 0x01
|
||||
};
|
||||
|
||||
// RestoredData keeps certain temporary data to help with
|
||||
// the restoration process
|
||||
struct RestoredData {
|
||||
int FPS;
|
||||
// Unserialized bitmaps for dynamic surfaces
|
||||
std::vector<std::unique_ptr<Bitmap>> DynamicSurfaces;
|
||||
// Unserialized bitmaps for overlays (old-style saves)
|
||||
std::unordered_map<int, std::unique_ptr<Bitmap> > OverlayImages;
|
||||
// Scripts global data
|
||||
struct ScriptData {
|
||||
std::vector<char> Data;
|
||||
size_t Len;
|
||||
|
||||
ScriptData();
|
||||
};
|
||||
ScriptData GlobalScript;
|
||||
std::vector<ScriptData> ScriptModules;
|
||||
// Game state data (loaded ahead)
|
||||
uint32_t DoOnceCount;
|
||||
// Room data (has to be be preserved until room is loaded)
|
||||
PBitmap RoomBkgScene[MAX_ROOM_BGFRAMES];
|
||||
int16_t RoomLightLevels[MAX_ROOM_REGIONS];
|
||||
int32_t RoomTintLevels[MAX_ROOM_REGIONS];
|
||||
int16_t RoomZoomLevels1[MAX_WALK_AREAS];
|
||||
int16_t RoomZoomLevels2[MAX_WALK_AREAS];
|
||||
RoomVolumeMod RoomVolume;
|
||||
// Mouse cursor parameters
|
||||
int CursorID;
|
||||
int CursorMode;
|
||||
// General audio
|
||||
struct ChannelInfo {
|
||||
int ClipID = -1;
|
||||
int Flags = 0;
|
||||
int Pos = 0;
|
||||
int Priority = 0;
|
||||
int Repeat = 0;
|
||||
int Vol = 0;
|
||||
int VolAsPercent = 0;
|
||||
int Pan = 0;
|
||||
int Speed = 0;
|
||||
// since version 1
|
||||
int XSource = -1;
|
||||
int YSource = -1;
|
||||
int MaxDist = 0;
|
||||
};
|
||||
ChannelInfo AudioChans[TOTAL_AUDIO_CHANNELS];
|
||||
// Ambient sounds
|
||||
int DoAmbient[MAX_GAME_CHANNELS];
|
||||
// Viewport and camera data, has to be preserved and applied only after
|
||||
// room gets loaded, because we must clamp these to room parameters.
|
||||
struct ViewportData {
|
||||
int ID = -1;
|
||||
int Flags = 0;
|
||||
int Left = 0;
|
||||
int Top = 0;
|
||||
int Width = 0;
|
||||
int Height = 0;
|
||||
int ZOrder = 0;
|
||||
int CamID = -1;
|
||||
};
|
||||
struct CameraData {
|
||||
int ID = -1;
|
||||
int Flags = 0;
|
||||
int Left = 0;
|
||||
int Top = 0;
|
||||
int Width = 0;
|
||||
int Height = 0;
|
||||
};
|
||||
std::vector<ViewportData> Viewports;
|
||||
std::vector<CameraData> Cameras;
|
||||
bool LegacyViewCamera = false;
|
||||
int32_t Camera0_Flags = 0; // flags for primary camera, when data is read in legacy order
|
||||
|
||||
RestoredData();
|
||||
};
|
||||
|
||||
enum PluginSvgVersion {
|
||||
kPluginSvgVersion_Initial = 0,
|
||||
kPluginSvgVersion_36115 = 1,
|
||||
};
|
||||
|
||||
// Runs plugin events, requesting to read save data from the given stream.
|
||||
// NOTE: there's no error check in this function, because plugin API currently
|
||||
// does not let plugins report any errors when restoring their saved data.
|
||||
void ReadPluginSaveData(Stream *in, PluginSvgVersion svg_ver, soff_t max_size);
|
||||
// Runs plugin events, requesting to write save data into the given stream.
|
||||
void WritePluginSaveData(Stream *out);
|
||||
|
||||
} // namespace Engine
|
||||
} // namespace AGS
|
||||
} // namespace AGS3
|
||||
|
||||
#endif
|
||||
504
engines/ags/engine/game/savegame_v321.cpp
Normal file
504
engines/ags/engine/game/savegame_v321.cpp
Normal file
@@ -0,0 +1,504 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// These functions are restoring old savegames used by engines 3.2.1 - 3.5.0.
|
||||
// The main point of keeping these today are to be able to compare game
|
||||
// behavior when running with original/older binaries and latest engine.
|
||||
// Perhaps the optimal solution would be to have a savegame converter instead.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/core/types.h"
|
||||
#include "ags/engine/ac/character_extras.h"
|
||||
#include "ags/shared/ac/common.h"
|
||||
#include "ags/engine/ac/dialog.h"
|
||||
#include "ags/engine/ac/button.h"
|
||||
#include "ags/engine/ac/dynamic_sprite.h"
|
||||
#include "ags/engine/ac/game.h"
|
||||
#include "ags/shared/ac/game_setup_struct.h"
|
||||
#include "ags/engine/ac/game_state.h"
|
||||
#include "ags/engine/ac/game_setup.h"
|
||||
#include "ags/engine/ac/move_list.h"
|
||||
#include "ags/engine/ac/overlay.h"
|
||||
#include "ags/shared/ac/sprite_cache.h"
|
||||
#include "ags/engine/ac/room_status.h"
|
||||
#include "ags/shared/ac/view.h"
|
||||
#include "ags/engine/ac/dynobj/cc_serializer.h"
|
||||
#include "ags/engine/ac/dynobj/dynobj_manager.h"
|
||||
#include "ags/engine/game/savegame.h"
|
||||
#include "ags/engine/game/savegame_components.h"
|
||||
#include "ags/engine/game/savegame_internal.h"
|
||||
#include "ags/engine/gui/animating_gui_button.h"
|
||||
#include "ags/shared/gui/gui_main.h"
|
||||
#include "ags/shared/gui/gui_button.h"
|
||||
#include "ags/shared/gui/gui_inv.h"
|
||||
#include "ags/shared/gui/gui_label.h"
|
||||
#include "ags/shared/gui/gui_listbox.h"
|
||||
#include "ags/shared/gui/gui_slider.h"
|
||||
#include "ags/shared/gui/gui_textbox.h"
|
||||
#include "ags/engine/media/audio/audio.h"
|
||||
#include "ags/plugins/plugin_engine.h"
|
||||
#include "ags/engine/script/script.h"
|
||||
#include "ags/shared/script/cc_common.h"
|
||||
#include "ags/shared/util/string_utils.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace AGS::Shared;
|
||||
using namespace AGS::Engine;
|
||||
|
||||
static const uint32_t MAGICNUMBER = 0xbeefcafe;
|
||||
|
||||
inline bool AssertGameContent(HSaveError &err, int game_val, int sav_val, const char *content_name, bool warn_only = false) {
|
||||
if (game_val != sav_val) {
|
||||
String msg = String::FromFormat("Mismatching number of %s (game: %d, save: %d).", content_name, game_val, sav_val);
|
||||
if (warn_only)
|
||||
Debug::Printf(kDbgMsg_Warn, "WARNING: restored save may be incompatible: %s", msg.GetCStr());
|
||||
else
|
||||
err = new SavegameError(kSvgErr_GameContentAssertion, msg);
|
||||
}
|
||||
return warn_only || (game_val == sav_val);
|
||||
}
|
||||
|
||||
template<typename TObject>
|
||||
inline bool AssertAndCopyGameContent(const std::vector<TObject> &old_list, std::vector<TObject> &new_list,
|
||||
HSaveError &err, const char *content_name, bool warn_only = false) {
|
||||
if (!AssertGameContent(err, old_list.size(), new_list.size(), content_name, warn_only))
|
||||
return false;
|
||||
|
||||
if (new_list.size() < old_list.size()) {
|
||||
size_t copy_at = new_list.size();
|
||||
new_list.resize(old_list.size());
|
||||
Common::copy(old_list.begin() + copy_at, old_list.end(), new_list.begin() + copy_at);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static HSaveError restore_game_head_dynamic_values(Stream *in, RestoredData &r_data) {
|
||||
r_data.FPS = in->ReadInt32();
|
||||
r_data.CursorMode = in->ReadInt32();
|
||||
r_data.CursorID = in->ReadInt32();
|
||||
SavegameComponents::ReadLegacyCameraState(in, r_data);
|
||||
set_loop_counter(in->ReadInt32());
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
static void restore_game_spriteset(Stream *in) {
|
||||
// ensure the sprite set is at least as large as it was
|
||||
// when the game was saved
|
||||
_GP(spriteset).EnlargeTo(in->ReadInt32() - 1); // they saved top_index + 1
|
||||
// get serialized dynamic sprites
|
||||
int sprnum = in->ReadInt32();
|
||||
while (sprnum) {
|
||||
unsigned char spriteflag = in->ReadInt8();
|
||||
std::unique_ptr<Bitmap> image(read_serialized_bitmap(in));
|
||||
add_dynamic_sprite(sprnum, std::move(image));
|
||||
_GP(game).SpriteInfos[sprnum].Flags = spriteflag;
|
||||
sprnum = in->ReadInt32();
|
||||
}
|
||||
}
|
||||
|
||||
static HSaveError restore_game_scripts(Stream *in, const PreservedParams &pp, RestoredData &r_data) {
|
||||
HSaveError err;
|
||||
// read the global script data segment
|
||||
size_t gdatasize = (uint32_t)in->ReadInt32();
|
||||
if (!AssertGameContent(err, pp.GlScDataSize, gdatasize, "global script data"))
|
||||
return err;
|
||||
r_data.GlobalScript.Len = gdatasize;
|
||||
r_data.GlobalScript.Data.resize(gdatasize);
|
||||
if (gdatasize > 0)
|
||||
in->Read(&r_data.GlobalScript.Data.front(), gdatasize);
|
||||
|
||||
uint32_t num_modules = (uint32_t)in->ReadInt32();
|
||||
if (!AssertGameContent(err, _G(numScriptModules), num_modules, "Script Modules"))
|
||||
return err;
|
||||
r_data.ScriptModules.resize(_G(numScriptModules));
|
||||
for (size_t i = 0; i < _G(numScriptModules); ++i) {
|
||||
size_t module_size = (uint32_t)in->ReadInt32();
|
||||
if (pp.ScMdDataSize[i] != module_size) {
|
||||
return new SavegameError(kSvgErr_GameContentAssertion, String::FromFormat("Mismatching size of script module data, module %zu.", i));
|
||||
}
|
||||
r_data.ScriptModules[i].Len = module_size;
|
||||
r_data.ScriptModules[i].Data.resize(module_size);
|
||||
if (module_size > 0)
|
||||
in->Read(&r_data.ScriptModules[i].Data.front(), module_size);
|
||||
}
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
static void restore_game_room_state(Stream *in, GameDataVersion data_ver) {
|
||||
_G(displayed_room) = in->ReadInt32();
|
||||
|
||||
// read the room state for all the rooms the player has been in
|
||||
for (int vv = 0; vv < MAX_ROOMS; vv++) {
|
||||
int beenhere = in->ReadInt8();
|
||||
if (beenhere) {
|
||||
RoomStatus *roomstat = getRoomStatus(vv);
|
||||
roomstat->beenhere = beenhere;
|
||||
|
||||
if (roomstat->beenhere) {
|
||||
roomstat->ReadFromSavegame_v321(in, data_ver);
|
||||
if (roomstat->tsdatasize > 0) {
|
||||
roomstat->tsdata.resize(roomstat->tsdatasize);
|
||||
in->Read(roomstat->tsdata.data(), roomstat->tsdatasize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_game_play(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
|
||||
int screenfadedout_was = _GP(play).screen_is_faded_out;
|
||||
int roomchanges_was = _GP(play).room_changes;
|
||||
|
||||
_GP(play).ReadFromSavegame(in, data_ver, kGSSvgVersion_OldFormat, r_data);
|
||||
r_data.Cameras[0].Flags = r_data.Camera0_Flags;
|
||||
|
||||
_GP(play).screen_is_faded_out = screenfadedout_was;
|
||||
_GP(play).room_changes = roomchanges_was;
|
||||
|
||||
char rbuffer[200]; // old doonceonly token length
|
||||
for (size_t i = 0; i < r_data.DoOnceCount; ++i) {
|
||||
StrUtil::ReadCStr(rbuffer, in, sizeof(rbuffer));
|
||||
_GP(play).do_once_tokens.insert(rbuffer);
|
||||
}
|
||||
|
||||
// Skip gui_draw_order (no longer applied from saves)
|
||||
in->Seek(_GP(game).numgui * sizeof(int32_t));
|
||||
}
|
||||
|
||||
static void restore_game_palette(Stream *in) {
|
||||
in->ReadArray(&_G(palette)[0], sizeof(RGB), 256);
|
||||
}
|
||||
|
||||
static void restore_game_dialogs(Stream *in) {
|
||||
for (int vv = 0; vv < _GP(game).numdialog; vv++)
|
||||
in->ReadArrayOfInt32(&_G(dialog)[vv].optionflags[0], MAXTOPICOPTIONS);
|
||||
}
|
||||
|
||||
static void restore_game_more_dynamic_values(Stream *in) {
|
||||
_G(mouse_on_iface) = in->ReadInt32();
|
||||
in->ReadInt32(); // mouse_on_iface_button
|
||||
in->ReadInt32(); // mouse_pushed_iface
|
||||
_G(ifacepopped) = in->ReadInt32();
|
||||
_G(game_paused) = in->ReadInt32();
|
||||
}
|
||||
|
||||
static HSaveError restore_game_gui(Stream *in) {
|
||||
// Legacy saves allowed to resize gui lists, and stored full gui data
|
||||
// (could be unintentional side effect). Here we emulate this for
|
||||
// upgraded games by letting read **less** data from saves, and copying
|
||||
// missing elements from reserved game data.
|
||||
const std::vector<GUIMain> res_guis = std::move(_GP(guis));
|
||||
const std::vector<GUIButton> res_guibuts = std::move(_GP(guibuts));
|
||||
const std::vector<GUIInvWindow> res_guiinv = std::move(_GP(guiinv));
|
||||
const std::vector<GUILabel> res_guilabels = std::move(_GP(guilabels));
|
||||
const std::vector<GUIListBox> res_guilist = std::move(_GP(guilist));
|
||||
const std::vector<GUISlider> res_guislider = std::move(_GP(guislider));
|
||||
const std::vector<GUITextBox> res_guitext = std::move(_GP(guitext));
|
||||
|
||||
HError guierr = GUI::ReadGUI(in, true);
|
||||
if (!guierr)
|
||||
return new SavegameError(kSvgErr_GameObjectInitFailed, guierr);
|
||||
|
||||
HSaveError err;
|
||||
const bool warn_only = _GP(usetup).legacysave_let_gui_diff;
|
||||
if (!AssertAndCopyGameContent(res_guis, _GP(guis), err, "GUIs", warn_only) ||
|
||||
!AssertAndCopyGameContent(res_guibuts, _GP(guibuts), err, "GUI Buttons", warn_only) ||
|
||||
!AssertAndCopyGameContent(res_guiinv, _GP(guiinv), err, "GUI InvWindows", warn_only) ||
|
||||
!AssertAndCopyGameContent(res_guilabels, _GP(guilabels), err, "GUI Labels", warn_only) ||
|
||||
!AssertAndCopyGameContent(res_guilist, _GP(guilist), err, "GUI ListBoxes", warn_only) ||
|
||||
!AssertAndCopyGameContent(res_guislider, _GP(guislider), err, "GUI Sliders", warn_only) ||
|
||||
!AssertAndCopyGameContent(res_guitext, _GP(guitext), err, "GUI TextBoxes", warn_only))
|
||||
|
||||
return err;
|
||||
GUI::RebuildGUI(); // rebuild guis in case they were copied from reserved game data
|
||||
_GP(game).numgui = _GP(guis).size();
|
||||
|
||||
int anim_count = in->ReadInt32();
|
||||
for (int i = 0; i < anim_count; ++i) {
|
||||
AnimatingGUIButton abtn;
|
||||
abtn.ReadFromSavegame(in, 0);
|
||||
AddButtonAnimation(abtn);
|
||||
}
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
static HSaveError restore_game_audiocliptypes(Stream *in) {
|
||||
HSaveError err;
|
||||
if (!AssertGameContent(err, _GP(game).audioClipTypes.size(), (uint32_t)in->ReadInt32(), "Audio Clip Types"))
|
||||
return err;
|
||||
|
||||
for (size_t i = 0; i < _GP(game).audioClipTypes.size(); ++i) {
|
||||
_GP(game).audioClipTypes[i].ReadFromFile(in);
|
||||
}
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
static void restore_game_thisroom(Stream *in, RestoredData &r_data) {
|
||||
in->ReadArrayOfInt16(r_data.RoomLightLevels, MAX_ROOM_REGIONS);
|
||||
in->ReadArrayOfInt32(r_data.RoomTintLevels, MAX_ROOM_REGIONS);
|
||||
in->ReadArrayOfInt16(r_data.RoomZoomLevels1, MAX_WALK_AREAS);
|
||||
in->ReadArrayOfInt16(r_data.RoomZoomLevels2, MAX_WALK_AREAS);
|
||||
}
|
||||
|
||||
static void restore_game_ambientsounds(Stream *in, RestoredData &r_data) {
|
||||
for (int i = 0; i < MAX_GAME_CHANNELS_v320; ++i) {
|
||||
_GP(ambient)[i].ReadFromFile(in);
|
||||
}
|
||||
|
||||
for (int bb = NUM_SPEECH_CHANS; bb < MAX_GAME_CHANNELS_v320; bb++) {
|
||||
if (_GP(ambient)[bb].channel == 0)
|
||||
r_data.DoAmbient[bb] = 0;
|
||||
else {
|
||||
r_data.DoAmbient[bb] = _GP(ambient)[bb].num;
|
||||
_GP(ambient)[bb].channel = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ReadOverlays_Aligned(Stream *in, std::vector<int> &has_bitmap, size_t num_overs) {
|
||||
// Remember that overlay indexes may be non-sequential
|
||||
auto &overs = get_overlays();
|
||||
for (size_t i = 0; i < num_overs; ++i) {
|
||||
bool has_bm;
|
||||
ScreenOverlay over;
|
||||
over.ReadFromSavegame(in, has_bm, -1);
|
||||
if (over.type < 0)
|
||||
continue; // safety abort
|
||||
if (overs.size() <= static_cast<uint32_t>(over.type))
|
||||
overs.resize(over.type + 1);
|
||||
overs[over.type] = std::move(over);
|
||||
if (has_bm)
|
||||
has_bitmap.push_back(over.type);
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_game_overlays(Stream *in, RestoredData &r_data) {
|
||||
size_t num_overs = in->ReadInt32();
|
||||
// Remember that overlay indexes may be not sequential,
|
||||
// the vector may be resized during read
|
||||
auto &overs = get_overlays();
|
||||
overs.resize(num_overs);
|
||||
std::vector<int> has_bitmap;
|
||||
ReadOverlays_Aligned(in, has_bitmap, num_overs);
|
||||
for (auto over_id : has_bitmap) {
|
||||
r_data.OverlayImages[over_id].reset(read_serialized_bitmap(in));
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_game_dynamic_surfaces(Stream *in, RestoredData &r_data) {
|
||||
// load into a temp array since ccUnserialiseObjects will destroy
|
||||
// it otherwise
|
||||
r_data.DynamicSurfaces.resize(MAX_DYNAMIC_SURFACES);
|
||||
for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) {
|
||||
if (in->ReadInt8() == 0) {
|
||||
r_data.DynamicSurfaces[i].reset();
|
||||
} else {
|
||||
r_data.DynamicSurfaces[i].reset(read_serialized_bitmap(in));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_game_displayed_room_status(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
|
||||
int bb;
|
||||
for (bb = 0; bb < MAX_ROOM_BGFRAMES; bb++)
|
||||
r_data.RoomBkgScene[bb].reset();
|
||||
|
||||
if (_G(displayed_room) >= 0) {
|
||||
|
||||
for (bb = 0; bb < MAX_ROOM_BGFRAMES; bb++) {
|
||||
r_data.RoomBkgScene[bb] = nullptr;
|
||||
if (_GP(play).raw_modified[bb]) {
|
||||
r_data.RoomBkgScene[bb].reset(read_serialized_bitmap(in));
|
||||
}
|
||||
}
|
||||
bb = in->ReadInt32();
|
||||
|
||||
if (bb)
|
||||
_G(raw_saved_screen).reset(read_serialized_bitmap(in));
|
||||
|
||||
// get the current troom, in case they save in room 600 or whatever
|
||||
_GP(troom).ReadFromSavegame_v321(in, data_ver);
|
||||
|
||||
if (_GP(troom).tsdatasize > 0) {
|
||||
_GP(troom).tsdata.resize(_GP(troom).tsdatasize);
|
||||
in->Read(_GP(troom).tsdata.data(), _GP(troom).tsdatasize);
|
||||
} else
|
||||
_GP(troom).tsdata.clear();
|
||||
}
|
||||
}
|
||||
|
||||
static HSaveError restore_game_globalvars(Stream *in) {
|
||||
HSaveError err;
|
||||
if (!AssertGameContent(err, _G(numGlobalVars), in->ReadInt32(), "Global Variables"))
|
||||
return err;
|
||||
|
||||
for (int i = 0; i < _G(numGlobalVars); ++i) {
|
||||
_G(globalvars)[i].Read(in);
|
||||
}
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
static HSaveError restore_game_views(Stream *in) {
|
||||
HSaveError err;
|
||||
if (!AssertGameContent(err, _GP(game).numviews, in->ReadInt32(), "Views"))
|
||||
return err;
|
||||
|
||||
for (int bb = 0; bb < _GP(game).numviews; bb++) {
|
||||
for (int cc = 0; cc < _GP(views)[bb].numLoops; cc++) {
|
||||
for (int dd = 0; dd < _GP(views)[bb].loops[cc].numFrames; dd++) {
|
||||
_GP(views)[bb].loops[cc].frames[dd].sound = in->ReadInt32();
|
||||
_GP(views)[bb].loops[cc].frames[dd].pic = in->ReadInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
static HSaveError restore_game_audio_and_crossfade(Stream *in, GameDataVersion data_ver, RestoredData &r_data) {
|
||||
in->ReadInt32(); // audio clips count, ignore
|
||||
|
||||
for (int i = 0; i < TOTAL_AUDIO_CHANNELS_v320; ++i) {
|
||||
RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i];
|
||||
chan_info.Pos = 0;
|
||||
chan_info.ClipID = in->ReadInt32();
|
||||
if (chan_info.ClipID >= 0) {
|
||||
if ((size_t)chan_info.ClipID >= _GP(game).audioClips.size()) {
|
||||
return new SavegameError(kSvgErr_GameObjectInitFailed,
|
||||
String::FromFormat("Invalid audio clip index %zu (valid range is 0..%zu)",
|
||||
(size_t)chan_info.ClipID, _GP(game).audioClips.size() - 1));
|
||||
}
|
||||
|
||||
chan_info.Pos = in->ReadInt32();
|
||||
if (chan_info.Pos < 0)
|
||||
chan_info.Pos = 0;
|
||||
chan_info.Priority = in->ReadInt32();
|
||||
chan_info.Repeat = in->ReadInt32();
|
||||
chan_info.Vol = in->ReadInt32();
|
||||
in->ReadInt32(); // unused
|
||||
chan_info.VolAsPercent = in->ReadInt32();
|
||||
chan_info.Pan = in->ReadInt32();
|
||||
chan_info.Speed = 1000;
|
||||
if (data_ver >= kGameVersion_340_2)
|
||||
chan_info.Speed = in->ReadInt32();
|
||||
}
|
||||
}
|
||||
_G(crossFading) = in->ReadInt32();
|
||||
_G(crossFadeVolumePerStep) = in->ReadInt32();
|
||||
_G(crossFadeStep) = in->ReadInt32();
|
||||
_G(crossFadeVolumeAtStart) = in->ReadInt32();
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
HSaveError restore_save_data_v321(Stream *in, GameDataVersion data_ver, const PreservedParams &pp, RestoredData &r_data) {
|
||||
HSaveError err = restore_game_head_dynamic_values(in, r_data);
|
||||
if (!err)
|
||||
return err;
|
||||
restore_game_spriteset(in);
|
||||
|
||||
err = restore_game_scripts(in, pp, r_data);
|
||||
if (!err)
|
||||
return err;
|
||||
restore_game_room_state(in, data_ver);
|
||||
restore_game_play(in, data_ver, r_data);
|
||||
// Global character movelists
|
||||
for (int i = 0; i < _GP(game).numcharacters + MAX_ROOM_OBJECTS_v300 + 1; ++i) {
|
||||
_GP(mls)[i].ReadFromSavegame_Legacy(in);
|
||||
}
|
||||
|
||||
// List of game objects, used to compare with the save contents
|
||||
struct ObjectCounts {
|
||||
int CharacterCount = _GP(game).numcharacters;
|
||||
int DialogCount = _GP(game).numdialog;
|
||||
int InvItemCount = _GP(game).numinvitems;
|
||||
int ViewCount = _GP(game).numviews;
|
||||
} objwas;
|
||||
|
||||
GameSetupStruct::SerializeInfo info;
|
||||
_GP(game).GameSetupStructBase::ReadFromFile(in, data_ver, info);
|
||||
|
||||
if (!AssertGameContent(err, objwas.CharacterCount, _GP(game).numcharacters, "Characters") ||
|
||||
!AssertGameContent(err, objwas.DialogCount, _GP(game).numdialog, "Dialogs") ||
|
||||
!AssertGameContent(err, objwas.InvItemCount, _GP(game).numinvitems, "Inventory Items") ||
|
||||
!AssertGameContent(err, objwas.ViewCount, _GP(game).numviews, "Views"))
|
||||
return err;
|
||||
|
||||
_GP(game).ReadFromSaveGame_v321(in);
|
||||
|
||||
// Modified custom properties are read separately to keep existing save format
|
||||
_GP(play).ReadCustomProperties_v340(in, data_ver);
|
||||
|
||||
// Character extras (runtime only data)
|
||||
for (int i = 0; i < _GP(game).numcharacters; ++i) {
|
||||
_GP(charextra)[i].ReadFromSavegame(in, kCharSvgVersion_Initial);
|
||||
}
|
||||
restore_game_palette(in);
|
||||
restore_game_dialogs(in);
|
||||
restore_game_more_dynamic_values(in);
|
||||
err = restore_game_gui(in);
|
||||
if (!err)
|
||||
return err;
|
||||
err = restore_game_audiocliptypes(in);
|
||||
if (!err)
|
||||
return err;
|
||||
restore_game_thisroom(in, r_data);
|
||||
restore_game_ambientsounds(in, r_data);
|
||||
restore_game_overlays(in, r_data);
|
||||
restore_game_dynamic_surfaces(in, r_data);
|
||||
restore_game_displayed_room_status(in, data_ver, r_data);
|
||||
err = restore_game_globalvars(in);
|
||||
if (!err)
|
||||
return err;
|
||||
err = restore_game_views(in);
|
||||
if (!err)
|
||||
return err;
|
||||
|
||||
if (static_cast<uint32_t>(in->ReadInt32()) != (MAGICNUMBER + 1)) {
|
||||
return new SavegameError(kSvgErr_InconsistentFormat, "Audio section header expected but not found.");
|
||||
}
|
||||
|
||||
err = restore_game_audio_and_crossfade(in, data_ver, r_data);
|
||||
if (!err)
|
||||
return err;
|
||||
|
||||
ReadPluginSaveData(in, kPluginSvgVersion_Initial, SIZE_MAX);
|
||||
if (static_cast<uint32_t>(in->ReadInt32()) != MAGICNUMBER)
|
||||
return new SavegameError(kSvgErr_InconsistentPlugin);
|
||||
|
||||
// save the new room music vol for later use
|
||||
r_data.RoomVolume = (RoomVolumeMod)in->ReadInt32();
|
||||
|
||||
if (ccUnserializeAllObjects(in, &_GP(ccUnserializer))) {
|
||||
return new SavegameError(kSvgErr_GameObjectInitFailed,
|
||||
String::FromFormat("Managed pool deserialization failed: %s.", cc_get_error().ErrorString.GetCStr()));
|
||||
}
|
||||
|
||||
// preserve legacy music type setting
|
||||
_G(current_music_type) = in->ReadInt32();
|
||||
|
||||
return HSaveError::None();
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
221
engines/ags/engine/game/viewport.cpp
Normal file
221
engines/ags/engine/game/viewport.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ags/engine/ac/draw.h"
|
||||
#include "ags/engine/ac/game_state.h"
|
||||
#include "ags/engine/debugging/debug_log.h"
|
||||
#include "ags/shared/game/room_struct.h"
|
||||
#include "ags/engine/game/viewport.h"
|
||||
#include "ags/globals.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
using namespace AGS::Shared;
|
||||
|
||||
void Camera::SetID(int id) {
|
||||
_id = id;
|
||||
}
|
||||
|
||||
// Returns Room camera position and size inside the room (in room coordinates)
|
||||
const Rect &Camera::GetRect() const {
|
||||
return _position;
|
||||
}
|
||||
|
||||
// Sets explicit room camera's orthographic size
|
||||
void Camera::SetSize(const Size cam_size) {
|
||||
// TODO: currently we don't support having camera larger than room background
|
||||
// (or rather - looking outside of the room background); look into this later
|
||||
const Size real_room_sz = (_G(displayed_room) >= 0 && (_GP(thisroom).Width > 0 && _GP(thisroom).Height > 0)) ?
|
||||
Size(data_to_game_coord(_GP(thisroom).Width), data_to_game_coord(_GP(thisroom).Height)) :
|
||||
Size(INT32_MAX, INT32_MAX);
|
||||
|
||||
const Size real_size = Size::Clamp(cam_size, Size(1, 1), real_room_sz);
|
||||
if (_position.GetWidth() == real_size.Width && _position.GetHeight() == real_size.Height)
|
||||
return;
|
||||
|
||||
_position.SetWidth(real_size.Width);
|
||||
_position.SetHeight(real_size.Height);
|
||||
SetAt(_position.Left, _position.Top); // readjust in case went off-room after size changed
|
||||
for (auto vp = _viewportRefs.begin(); vp != _viewportRefs.end(); ++vp) {
|
||||
auto locked_vp = vp->lock();
|
||||
if (locked_vp)
|
||||
locked_vp->AdjustTransformation();
|
||||
}
|
||||
_hasChangedSize = true;
|
||||
}
|
||||
|
||||
// Puts room camera to the new location in the room
|
||||
void Camera::SetAt(int x, int y) {
|
||||
int cw = _position.GetWidth();
|
||||
int ch = _position.GetHeight();
|
||||
int room_width = data_to_game_coord(_GP(thisroom).Width);
|
||||
int room_height = data_to_game_coord(_GP(thisroom).Height);
|
||||
x = Math::Clamp(x, 0, room_width - cw);
|
||||
y = Math::Clamp(y, 0, room_height - ch);
|
||||
if (_position.Left == x && _position.Top == y)
|
||||
return;
|
||||
_position.MoveTo(Point(x, y));
|
||||
_hasChangedPosition = true;
|
||||
}
|
||||
|
||||
// Tells if camera is currently locked at custom position
|
||||
bool Camera::IsLocked() const {
|
||||
return _locked;
|
||||
}
|
||||
|
||||
// Locks room camera at its current position
|
||||
void Camera::Lock() {
|
||||
debug_script_log("Room camera locked");
|
||||
_locked = true;
|
||||
}
|
||||
|
||||
// Similar to SetAt, but also locks camera preventing it from following player character
|
||||
void Camera::LockAt(int x, int y) {
|
||||
debug_script_log("Room camera locked to %d,%d", x, y);
|
||||
SetAt(x, y);
|
||||
_locked = true;
|
||||
}
|
||||
|
||||
// Releases camera lock, letting it follow player character
|
||||
void Camera::Release() {
|
||||
_locked = false;
|
||||
debug_script_log("Room camera released back to engine control");
|
||||
}
|
||||
|
||||
// Link this camera to a new viewport; this does not unlink any linked ones
|
||||
void Camera::LinkToViewport(ViewportRef viewport) {
|
||||
auto new_locked = viewport.lock();
|
||||
if (!new_locked)
|
||||
return;
|
||||
for (auto vp = _viewportRefs.begin(); vp != _viewportRefs.end(); ++vp) {
|
||||
auto old_locked = vp->lock();
|
||||
if (old_locked->GetID() == new_locked->GetID())
|
||||
return;
|
||||
}
|
||||
_viewportRefs.push_back(viewport);
|
||||
}
|
||||
|
||||
// Unlinks this camera from a given viewport; does nothing if link did not exist
|
||||
void Camera::UnlinkFromViewport(int id) {
|
||||
for (auto vp = _viewportRefs.begin(); vp != _viewportRefs.end(); ++vp) {
|
||||
auto locked = vp->lock();
|
||||
if (locked && locked->GetID() == id) {
|
||||
_viewportRefs.erase(vp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ViewportRef> &Camera::GetLinkedViewports() const {
|
||||
return _viewportRefs;
|
||||
}
|
||||
|
||||
void Viewport::SetID(int id) {
|
||||
_id = id;
|
||||
}
|
||||
|
||||
void Viewport::SetRect(const Rect &rc) {
|
||||
// TODO: consider allowing size 0,0, in which case viewport is considered not visible
|
||||
Size fix_size = rc.GetSize().IsNull() ? Size(1, 1) : rc.GetSize();
|
||||
Rect new_pos = RectWH(rc.Left, rc.Top, fix_size.Width, fix_size.Height);
|
||||
if (new_pos == _position)
|
||||
return;
|
||||
_position = new_pos;
|
||||
AdjustTransformation();
|
||||
_hasChangedPosition = true;
|
||||
_hasChangedSize = true;
|
||||
}
|
||||
|
||||
void Viewport::SetSize(const Size sz) {
|
||||
// TODO: consider allowing size 0,0, in which case viewport is considered not visible
|
||||
Size fix_size = sz.IsNull() ? Size(1, 1) : sz;
|
||||
if (_position.GetWidth() == fix_size.Width && _position.GetHeight() == fix_size.Height)
|
||||
return;
|
||||
_position = RectWH(_position.Left, _position.Top, fix_size.Width, fix_size.Height);
|
||||
AdjustTransformation();
|
||||
_hasChangedSize = true;
|
||||
}
|
||||
|
||||
void Viewport::SetAt(int x, int y) {
|
||||
if (_position.Left == x && _position.Top == y)
|
||||
return;
|
||||
_position.MoveTo(Point(x, y));
|
||||
AdjustTransformation();
|
||||
_hasChangedPosition = true;
|
||||
}
|
||||
|
||||
void Viewport::SetVisible(bool on) {
|
||||
_visible = on;
|
||||
_hasChangedVisible = true;
|
||||
}
|
||||
|
||||
void Viewport::SetZOrder(int zorder) {
|
||||
_zorder = zorder;
|
||||
_hasChangedVisible = true;
|
||||
}
|
||||
|
||||
void Viewport::AdjustTransformation() {
|
||||
auto locked_cam = _camera.lock();
|
||||
if (locked_cam)
|
||||
_transform.Init(locked_cam->GetRect().GetSize(), _position);
|
||||
}
|
||||
|
||||
PCamera Viewport::GetCamera() const {
|
||||
return _camera.lock();
|
||||
}
|
||||
|
||||
void Viewport::LinkCamera(PCamera cam) {
|
||||
_camera = cam;
|
||||
AdjustTransformation();
|
||||
}
|
||||
|
||||
VpPoint Viewport::RoomToScreen(int roomx, int roomy, bool clip) const {
|
||||
auto cam = _camera.lock();
|
||||
if (!cam)
|
||||
return std::make_pair(Point(), -1);
|
||||
const Rect &camr = cam->GetRect();
|
||||
Point screen_pt = _transform.Scale(Point(roomx - camr.Left, roomy - camr.Top));
|
||||
if (clip && !_position.IsInside(screen_pt))
|
||||
return std::make_pair(Point(), -1);
|
||||
return std::make_pair(screen_pt, _id);
|
||||
}
|
||||
|
||||
VpPoint Viewport::ScreenToRoom(int scrx, int scry, bool clip, bool convert_cam_to_data) const {
|
||||
Point screen_pt(scrx, scry);
|
||||
if (clip && !_position.IsInside(screen_pt))
|
||||
return std::make_pair(Point(), -1);
|
||||
auto cam = _camera.lock();
|
||||
if (!cam)
|
||||
return std::make_pair(Point(), -1);
|
||||
|
||||
const Rect &camr = cam->GetRect();
|
||||
Point p = _transform.UnScale(screen_pt);
|
||||
if (convert_cam_to_data) {
|
||||
p.X += game_to_data_coord(camr.Left);
|
||||
p.Y += game_to_data_coord(camr.Top);
|
||||
} else {
|
||||
p.X += camr.Left;
|
||||
p.Y += camr.Top;
|
||||
}
|
||||
return std::make_pair(p, _id);
|
||||
}
|
||||
|
||||
} // namespace AGS3
|
||||
226
engines/ags/engine/game/viewport.h
Normal file
226
engines/ags/engine/game/viewport.h
Normal file
@@ -0,0 +1,226 @@
|
||||
/* 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.
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
//=============================================================================
|
||||
//
|
||||
// Definition for the game viewports and cameras.
|
||||
//
|
||||
//=============================================================================
|
||||
|
||||
#ifndef AGS_ENGINE_GAME_VIEWPORT_H
|
||||
#define AGS_ENGINE_GAME_VIEWPORT_H
|
||||
|
||||
#include "common/std/memory.h"
|
||||
#include "common/std/vector.h"
|
||||
#include "ags/shared/util/geometry.h"
|
||||
#include "ags/shared/util/scaling.h"
|
||||
|
||||
namespace AGS3 {
|
||||
|
||||
class Camera;
|
||||
class Viewport;
|
||||
|
||||
typedef std::shared_ptr<Camera> PCamera;
|
||||
typedef std::shared_ptr<Viewport> PViewport;
|
||||
typedef std::weak_ptr<Camera> CameraRef;
|
||||
typedef std::weak_ptr<Viewport> ViewportRef;
|
||||
|
||||
// TODO: move to utility header
|
||||
// From https://stackoverflow.com/questions/45507041/how-to-check-if-weak-ptr-is-empty-non-assigned
|
||||
// Tests that weak_ptr is empty (was not initialized with valid reference)
|
||||
template <typename T>
|
||||
bool is_uninitialized(std::weak_ptr<T> const &weak) {
|
||||
using wt = std::weak_ptr<T>;
|
||||
return !weak.owner_before(wt{}) &&!wt{} .owner_before(weak);
|
||||
}
|
||||
|
||||
|
||||
// Camera defines a "looking eye" inside the room, its position and size.
|
||||
// It does not render anywhere on its own, instead it is linked to a viewport
|
||||
// and latter draws what that camera sees.
|
||||
// One camera may be linked to multiple viewports.
|
||||
// Camera does not move on its own, this is performed by separate behavior
|
||||
// algorithm. But it provides "lock" property that tells if its position is
|
||||
// currently owned by user script.
|
||||
class Camera {
|
||||
public:
|
||||
// Gets camera ID (serves as an index)
|
||||
inline int GetID() const {
|
||||
return _id;
|
||||
}
|
||||
// Sets new camera ID
|
||||
void SetID(int id);
|
||||
// Returns Room camera position and size inside the room (in room coordinates)
|
||||
const Rect &GetRect() const;
|
||||
// Sets explicit room camera's orthographic size
|
||||
void SetSize(const Size sz);
|
||||
// Puts room camera to the new location in the room
|
||||
void SetAt(int x, int y);
|
||||
// Tells if camera is currently locked at custom position
|
||||
bool IsLocked() const;
|
||||
// Locks room camera at its current position
|
||||
void Lock();
|
||||
// Similar to SetAt, but also locks camera preventing it from following player character
|
||||
void LockAt(int x, int y);
|
||||
// Releases camera lock, letting it follow player character
|
||||
void Release();
|
||||
|
||||
// Link this camera to a new viewport; this does not unlink any linked ones
|
||||
void LinkToViewport(ViewportRef viewport);
|
||||
// Unlinks this camera from a given viewport; does nothing if link did not exist
|
||||
void UnlinkFromViewport(int id);
|
||||
// Get the array of linked viewport references
|
||||
const std::vector<ViewportRef> &GetLinkedViewports() const;
|
||||
|
||||
// Tell if this camera has changed recently
|
||||
inline bool HasChangedPosition() const {
|
||||
return _hasChangedPosition;
|
||||
}
|
||||
inline bool HasChangedSize() const {
|
||||
return _hasChangedSize;
|
||||
}
|
||||
// Clears the changed flags
|
||||
void ClearChangedFlags() {
|
||||
_hasChangedPosition = false;
|
||||
_hasChangedSize = false;
|
||||
}
|
||||
|
||||
private:
|
||||
int _id = -1;
|
||||
// Actual position and orthographic size
|
||||
Rect _position;
|
||||
// Locked or following player automatically
|
||||
bool _locked = false;
|
||||
// Linked viewport refs, used to notify viewports of camera changes
|
||||
std::vector<ViewportRef> _viewportRefs;
|
||||
// Flags that tell whether this camera's position on screen has changed recently
|
||||
bool _hasChangedPosition = false;
|
||||
bool _hasChangedSize = false;
|
||||
};
|
||||
|
||||
|
||||
// A result of coordinate conversion between screen and the room,
|
||||
// tells which viewport was used to pass the "touch" through.
|
||||
typedef std::pair<Point, int> VpPoint;
|
||||
|
||||
|
||||
// Viewport class defines a rectangular area on game screen where the contents
|
||||
// of a Camera are rendered.
|
||||
// Viewport may have one linked camera at a time.
|
||||
class Viewport {
|
||||
public:
|
||||
// Gets viewport ID (serves as an index)
|
||||
inline int GetID() const {
|
||||
return _id;
|
||||
}
|
||||
// Sets new viewport ID
|
||||
void SetID(int id);
|
||||
// Returns viewport's position on screen
|
||||
inline const Rect &GetRect() const {
|
||||
return _position;
|
||||
}
|
||||
// Returns viewport's room-to-screen transformation
|
||||
inline const AGS::Shared::PlaneScaling &GetTransform() const {
|
||||
return _transform;
|
||||
}
|
||||
// Set viewport's rectangle on screen
|
||||
void SetRect(const Rect &rc);
|
||||
// Sets viewport size
|
||||
void SetSize(const Size sz);
|
||||
// Sets viewport's position on screen
|
||||
void SetAt(int x, int y);
|
||||
|
||||
// Tells whether viewport content is rendered on screen
|
||||
bool IsVisible() const {
|
||||
return _visible;
|
||||
}
|
||||
// Changes viewport visibility
|
||||
void SetVisible(bool on);
|
||||
// Gets the order viewport is displayed on screen
|
||||
int GetZOrder() const {
|
||||
return _zorder;
|
||||
}
|
||||
// Sets the viewport's z-order on screen
|
||||
void SetZOrder(int zorder);
|
||||
|
||||
// Calculates room-to-viewport coordinate conversion.
|
||||
void AdjustTransformation();
|
||||
// Returns linked camera
|
||||
PCamera GetCamera() const;
|
||||
// Links new camera to this viewport, overriding existing link;
|
||||
// pass nullptr to leave viewport without a camera link
|
||||
void LinkCamera(PCamera cam);
|
||||
|
||||
// TODO: provide a Transform object here that does these conversions instead
|
||||
// Converts room coordinates to the game screen coordinates through this viewport;
|
||||
// if clipping is on, the function will fail for room coordinates outside of camera
|
||||
VpPoint RoomToScreen(int roomx, int roomy, bool clip = false) const;
|
||||
// Converts game screen coordinates to the room coordinates through this viewport;
|
||||
// if clipping is on, the function will fail for screen coordinates outside of viewport;
|
||||
// convert_cam_to_data parameter converts camera "game" coordinates to "data" units (legacy mode)
|
||||
VpPoint ScreenToRoom(int scrx, int scry, bool clip = false, bool convert_cam_to_data = false) const;
|
||||
|
||||
// Following functions tell if this viewport has changed recently
|
||||
inline bool HasChangedPosition() const {
|
||||
return _hasChangedPosition;
|
||||
}
|
||||
inline bool HasChangedSize() const {
|
||||
return _hasChangedSize;
|
||||
}
|
||||
inline bool HasChangedVisible() const {
|
||||
return _hasChangedVisible;
|
||||
}
|
||||
inline void SetChangedVisible() {
|
||||
_hasChangedVisible = true;
|
||||
}
|
||||
// Clears the changed flags
|
||||
inline void ClearChangedFlags() {
|
||||
_hasChangedPosition = false;
|
||||
_hasChangedSize = false;
|
||||
_hasChangedVisible = false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Parameterized implementation of screen-to-room coordinate conversion
|
||||
VpPoint ScreenToRoomImpl(int scrx, int scry, bool clip, bool convert_cam_to_data);
|
||||
|
||||
int _id = -1;
|
||||
// Position of the viewport on screen
|
||||
Rect _position;
|
||||
// TODO: Camera reference (when supporting multiple cameras)
|
||||
// Coordinate transform between camera and viewport
|
||||
// TODO: need to add rotate conversion to let script API support that;
|
||||
// (maybe use full 3D matrix for that)
|
||||
AGS::Shared::PlaneScaling _transform;
|
||||
// Linked camera reference
|
||||
CameraRef _camera;
|
||||
bool _visible = true;
|
||||
int _zorder = 0;
|
||||
// Flags that tell whether this viewport's position on screen has changed recently
|
||||
bool _hasChangedPosition = false;
|
||||
bool _hasChangedOffscreen = false;
|
||||
bool _hasChangedSize = false;
|
||||
bool _hasChangedVisible = false;
|
||||
};
|
||||
|
||||
} // namespace AGS3
|
||||
|
||||
#endif // AGS_ENGINE_AC_VIEWPORT_H
|
||||
Reference in New Issue
Block a user