/* 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 . * */ #include "common/config-manager.h" #include "common/memstream.h" #include "ags/engine/ac/game.h" #include "ags/shared/ac/common.h" #include "ags/shared/ac/view.h" #include "ags/engine/ac/audio_channel.h" #include "ags/engine/ac/button.h" #include "ags/engine/ac/character.h" #include "ags/engine/ac/dialog.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_setup.h" #include "ags/shared/ac/game_setup_struct.h" #include "ags/engine/ac/game_state.h" #include "ags/engine/ac/global_audio.h" #include "ags/engine/ac/global_display.h" #include "ags/engine/ac/global_game.h" #include "ags/engine/ac/global_gui.h" #include "ags/engine/ac/global_object.h" #include "ags/engine/ac/global_translation.h" #include "ags/engine/ac/gui.h" #include "ags/engine/ac/hotspot.h" #include "ags/shared/ac/keycode.h" #include "ags/engine/ac/lip_sync.h" #include "ags/engine/ac/mouse.h" #include "ags/engine/ac/move_list.h" #include "ags/engine/ac/overlay.h" #include "ags/engine/ac/path_helper.h" #include "ags/engine/ac/sys_events.h" #include "ags/engine/ac/rich_game_media.h" #include "ags/engine/ac/room_status.h" #include "ags/engine/ac/sprite.h" #include "ags/shared/ac/sprite_cache.h" #include "ags/engine/ac/string.h" #include "ags/engine/ac/translation.h" #include "ags/engine/ac/dynobj/all_dynamic_classes.h" #include "ags/engine/ac/dynobj/all_script_classes.h" #include "ags/engine/ac/dynobj/script_camera.h" #include "ags/engine/ac/dynobj/script_game.h" #include "ags/engine/ac/dynobj/dynobj_manager.h" #include "ags/engine/debugging/debug_log.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/engine/game/savegame.h" #include "ags/shared/gfx/bitmap.h" #include "ags/engine/gfx/graphics_driver.h" #include "ags/shared/gui/gui_button.h" #include "ags/shared/gui/gui_slider.h" #include "ags/engine/gui/gui_dialog.h" #include "ags/engine/main/engine.h" #include "ags/engine/main/game_run.h" #include "ags/engine/media/audio/audio_system.h" #include "ags/engine/media/video/video.h" #include "ags/engine/platform/base/ags_platform_driver.h" #include "ags/engine/platform/base/sys_main.h" #include "ags/plugins/plugin_engine.h" #include "ags/engine/script/script.h" #include "ags/engine/script/script_runtime.h" #include "ags/shared/util/directory.h" #include "ags/shared/util/file.h" #include "ags/shared/util/path.h" #include "ags/shared/debugging/out.h" #include "ags/engine/script/script_api.h" #include "ags/engine/script/script_runtime.h" #include "ags/ags.h" #include "ags/globals.h" namespace AGS3 { using namespace AGS::Shared; using namespace AGS::Engine; //============================================================================= // Audio //============================================================================= void Game_StopAudio(int audioType) { if (((audioType < 0) || ((size_t)audioType >= _GP(game).audioClipTypes.size())) && (audioType != SCR_NO_VALUE)) quitprintf("!Game.StopAudio: invalid audio type %d", audioType); for (int aa = 0; aa < _GP(game).numGameChannels; aa++) { if (audioType == SCR_NO_VALUE) { stop_or_fade_out_channel(aa); } else { ScriptAudioClip *clip = AudioChannel_GetPlayingClip(&_G(scrAudioChannel)[aa]); if ((clip != nullptr) && (clip->type == audioType)) stop_or_fade_out_channel(aa); } } remove_clips_of_type_from_queue(audioType); } int Game_IsAudioPlaying(int audioType) { if (((audioType < 0) || ((size_t)audioType >= _GP(game).audioClipTypes.size())) && (audioType != SCR_NO_VALUE)) quitprintf("!Game.IsAudioPlaying: invalid audio type %d", audioType); if (_GP(play).fast_forward) return 0; for (int aa = 0; aa < _GP(game).numGameChannels; aa++) { ScriptAudioClip *clip = AudioChannel_GetPlayingClip(&_G(scrAudioChannel)[aa]); if (clip != nullptr) { if ((clip->type == audioType) || (audioType == SCR_NO_VALUE)) { return 1; } } } return 0; } void Game_SetAudioTypeSpeechVolumeDrop(int audioType, int volumeDrop) { if ((audioType < 0) || ((size_t)audioType >= _GP(game).audioClipTypes.size())) quitprintf("!Game.SetAudioTypeVolume: invalid audio type: %d", audioType); Debug::Printf("Game.SetAudioTypeSpeechVolumeDrop: type: %d, drop: %d", audioType, volumeDrop); _GP(game).audioClipTypes[audioType].volume_reduction_while_speech_playing = volumeDrop; update_volume_drop_if_voiceover(); } void Game_SetAudioTypeVolume(int audioType, int volume, int changeType) { if ((volume < 0) || (volume > 100)) quitprintf("!Game.SetAudioTypeVolume: volume %d is not between 0..100", volume); if ((audioType < 0) || ((size_t)audioType >= _GP(game).audioClipTypes.size())) quitprintf("!Game.SetAudioTypeVolume: invalid audio type: %d", audioType); const char *change_str[3]{"existing", "future", "all"}; Debug::Printf("Game.SetAudioTypeVolume: type: %d, volume: %d, change: %s", audioType, volume, change_str[changeType - VOL_CHANGEEXISTING]); if ((changeType == VOL_CHANGEEXISTING) || (changeType == VOL_BOTH)) { for (int aa = 0; aa < _GP(game).numGameChannels; aa++) { ScriptAudioClip *clip = AudioChannel_GetPlayingClip(&_G(scrAudioChannel)[aa]); if ((clip != nullptr) && (clip->type == audioType)) { auto *ch = AudioChans::GetChannel(aa); if (ch) ch->set_volume100(volume); } } } if ((changeType == VOL_SETFUTUREDEFAULT) || (changeType == VOL_BOTH)) { _GP(play).default_audio_type_volumes[audioType] = volume; // update queued clip volumes update_queued_clips_volume(audioType, volume); } } int Game_GetMODPattern() { if (_G(current_music_type) != MUS_MOD) return -1; auto *music_ch = AudioChans::GetChannelIfPlaying(SCHAN_MUSIC); return music_ch ? music_ch->get_pos() : -1; } //============================================================================= // --- //============================================================================= int Game_GetDialogCount() { return _GP(game).numdialog; } void set_debug_mode(bool on) { _GP(play).debug_mode = on ? 1 : 0; } void set_game_speed(int new_fps) { _G(frames_per_second) = new_fps; if (!isTimerFpsMaxed()) // if in maxed mode, don't update timer for now setTimerFps(new_fps); } float get_game_speed() { return _G(frames_per_second); } void setup_for_dialog() { _G(cbuttfont) = _GP(play).normal_font; _G(acdialog_font) = _GP(play).normal_font; _G(oldmouse) = _G(cur_cursor); set_mouse_cursor(CURS_ARROW); } void restore_after_dialog() { set_mouse_cursor(_G(oldmouse)); invalidate_screen(); } String get_save_game_directory() { return _G(saveGameDirectory); } String get_save_game_suffix() { return _G(saveGameSuffix); } void set_save_game_suffix(const String &suffix) { _G(saveGameSuffix) = suffix; } String get_save_game_filename(int slotNum) { return String(g_engine->getSaveStateName(slotNum).c_str()); } String get_save_game_path(int slotNum) { #if AGS_PLATFORM_SCUMMVM return Common::String::format("%s%s", SAVE_FOLDER_PREFIX, ::AGS::g_vm->getSaveStateName(slotNum).c_str()); #else return Path::ConcatPaths(_G(saveGameDirectory), get_save_game_filename(slotNum)); #endif } #if !AGS_PLATFORM_SCUMMVM // Convert a path possibly containing path tags into acceptable save path // NOTE that the game script may issue an order to change the save directory to // a dir of a new name. While we let this work, we also try to keep these // inside same parent location, would that be a common system directory, // or a custom one set by a player in config. static bool MakeSaveGameDir(const String &new_dir, FSLocation &fsdir) { fsdir = FSLocation(); // don't allow absolute paths if (!is_relative_filename(new_dir)) return false; String fixed_newdir = FixSlashAfterToken(new_dir); if (fixed_newdir.CompareLeft(UserSavedgamesRootToken, UserSavedgamesRootToken.GetLength()) == 0) { fixed_newdir.ClipLeft(UserSavedgamesRootToken.GetLength()); } else if (game.options[OPT_SAFEFILEPATHS] > 0) { // For games made in the safe-path-aware versions of AGS, report a warning debug_script_warn("Attempt to explicitly set savegame location relative to the game installation directory ('%s') denied;\nPath will be remapped to the user documents directory: '%s'", fixed_newdir.GetCStr(), fsdir.FullDir.GetCStr()); } // Resolve the new dir relative to the user data parent dir fsdir = GetGameUserDataDir().Concat(fixed_newdir); return true; } #endif // Tries to assign a new save directory, and copies the restart point if available static bool SetSaveGameDirectory(const FSLocation &fsdir) { if (!Directory::CreateAllDirectories(fsdir.BaseDir, fsdir.FullDir)) { debug_script_warn("SetSaveGameDirectory: failed to create all subdirectories: %s", fsdir.FullDir.GetCStr()); return false; } String newSaveGameDir = fsdir.FullDir; if (!File::TestCreateFile(Path::ConcatPaths(newSaveGameDir, "agstmp.tmp"))) return false; // copy the Restart Game file, if applicable String old_restart_path = Path::ConcatPaths(_G(saveGameDirectory), get_save_game_filename(RESTART_POINT_SAVE_GAME_NUMBER)); if (File::IsFile(old_restart_path)) { String new_restart_path = Path::ConcatPaths(newSaveGameDir, get_save_game_filename(RESTART_POINT_SAVE_GAME_NUMBER)); File::CopyFile(old_restart_path, new_restart_path, true); } _G(saveGameDirectory) = newSaveGameDir; return true; } void SetDefaultSaveDirectory() { // Request a default save location, and assign it as a save dir FSLocation fsdir = GetGameUserDataDir(); SetSaveGameDirectory(fsdir); } int Game_SetSaveGameDirectory(const char *newFolder) { #if AGS_PLATFORM_SCUMMVM return 1; #else // First resolve the script path (it may contain tokens) FSLocation fsdir; if (!MakeSaveGameDir(newFolder, fsdir)) return 0; // If resolved successfully, try to assign the new dir return SetSaveGameDirectory(fsdir) ? 1 : 0; #endif } const char *Game_GetSaveSlotDescription(int slnum) { String description; if (read_savedgame_description(get_save_game_path(slnum), description)) { return CreateNewScriptString(description.GetCStr()); } return nullptr; } void restore_game_dialog() { can_run_delayed_command(); if (_GP(thisroom).Options.SaveLoadDisabled == 1) { DisplayMessage(983); return; } if (_G(inside_script)) { _G(curscript)->queue_action(ePSARestoreGameDialog, 0, "RestoreGameDialog"); return; } do_restore_game_dialog(); } bool do_restore_game_dialog() { setup_for_dialog(); int toload = loadgamedialog(); restore_after_dialog(); if (toload >= 0) try_restore_save(toload); return toload >= 0; } void save_game_dialog() { if (_GP(thisroom).Options.SaveLoadDisabled == 1) { DisplayMessage(983); return; } if (_G(inside_script)) { _G(curscript)->queue_action(ePSASaveGameDialog, 0, "SaveGameDialog"); return; } do_save_game_dialog(); } bool do_save_game_dialog() { setup_for_dialog(); int toload = savegamedialog(); restore_after_dialog(); if (toload >= 0) save_game(toload, get_gui_dialog_buffer()); return toload >= 0; } void free_do_once_tokens() { _GP(play).do_once_tokens.clear(); } // Free all the memory associated with the game void unload_game() { dispose_game_drawdata(); // NOTE: fonts should be freed prior to stopping plugins, // as plugins may provide font renderer interface. free_all_fonts(); close_translation(); // NOTE: script objects must be freed prior to stopping plugins, // in case there are managed objects provided by plugins. ccRemoveAllSymbols(); ccUnregisterAllObjects(); pl_stop_plugins(); // Free all script instances and script modules FreeAllScriptInstances(); FreeGlobalScripts(); _GP(charextra).clear(); _GP(mls).clear(); _GP(views).clear(); _GP(splipsync).clear(); _G(numLipLines) = 0; _G(curLipLine) = -1; _G(dialog).clear(); _GP(scrDialog).clear(); _GP(guis).clear(); _GP(scrGui).clear(); get_overlays().clear(); resetRoomStatuses(); _GP(thisroom).Free(); // Free game state and game struct // Use placement new to keep the same memory locations // as these structures are still referenced and may be reused // in some games _G(play)->~GameState(); new(_G(play)) GameState(); _G(game)->~GameSetupStruct(); new(_G(game)) GameSetupStruct(); // Reset all resource caches // IMPORTANT: this is hard reset, including locked items _GP(spriteset).Reset(); } const char *Game_GetGlobalStrings(int index) { if ((index < 0) || (index >= MAXGLOBALSTRINGS)) quit("!Game.GlobalStrings: invalid index"); return CreateNewScriptString(_GP(play).globalstrings[index]); } // ** GetGameParameter replacement functions int Game_GetInventoryItemCount() { // because of the dummy item 0, this is always one higher than it should be return _GP(game).numinvitems - 1; } int Game_GetFontCount() { return _GP(game).numfonts; } int Game_GetMouseCursorCount() { return _GP(game).numcursors; } int Game_GetCharacterCount() { return _GP(game).numcharacters; } int Game_GetGUICount() { return _GP(game).numgui; } int Game_GetViewCount() { return _GP(game).numviews; } int Game_GetUseNativeCoordinates() { return _GP(game).IsDataInNativeCoordinates() ? 1 : 0; } int Game_GetSpriteWidth(int spriteNum) { if (spriteNum < 0) return 0; if (!_GP(spriteset).DoesSpriteExist(spriteNum)) return 0; return game_to_data_coord(_GP(game).SpriteInfos[spriteNum].Width); } int Game_GetSpriteHeight(int spriteNum) { if (spriteNum < 0) return 0; if (!_GP(spriteset).DoesSpriteExist(spriteNum)) return 0; return game_to_data_coord(_GP(game).SpriteInfos[spriteNum].Height); } void AssertView(const char *apiname, int view) { // NOTE: we assume (here and below) that the view is already in an internal 0-based range. // but when printing an error we will use (view + 1) for compliance with the script API. if ((view < 0) || (view >= _GP(game).numviews)) quitprintf("!%s: invalid view %d (range is 1..%d)", apiname, view + 1, _GP(game).numviews); } void AssertViewHasLoops(const char *apiname, int view) { AssertView(apiname, view); if (_GP(views)[view].numLoops == 0) quitprintf("!%s: view %d does not have any loops.", apiname, view + 1); } void AssertLoop(const char *apiname, int view, int loop) { AssertViewHasLoops(apiname, view); if ((loop < 0) || (loop >= _GP(views)[view].numLoops)) quitprintf("!%s: invalid loop number %d for view %d (range is 0..%d).", apiname, loop, view + 1, _GP(views)[view].numLoops - 1); } void AssertFrame(const char *apiname, int view, int loop, int frame) { AssertLoop(apiname, view, loop); if (_GP(views)[view].loops[loop].numFrames == 0) quitprintf("!%s: view %d loop %d does not have any frames", apiname, view + 1, loop); if ((frame < 0) || (frame >= _GP(views)[view].loops[loop].numFrames)) quitprintf("!%s: invalid frame number %d for view %d loop %d (range is 0..%d)", apiname, frame, view + 1, loop, _GP(views)[view].loops[loop].numFrames - 1); } int Game_GetLoopCountForView(int view) { view--; // convert to 0-based AssertView("Game.GetLoopCountForView", view); return _GP(views)[view].numLoops; } int Game_GetRunNextSettingForLoop(int view, int loop) { view--; // convert to 0-based AssertLoop("Game.GetRunNextSettingForLoop", view, loop); return (_GP(views)[view].loops[loop].RunNextLoop()) ? 1 : 0; } int Game_GetFrameCountForLoop(int view, int loop) { view--; // convert to 0-based AssertLoop("Game.GetFrameCountForLoop", view, loop); return _GP(views)[view].loops[loop].numFrames; } ScriptViewFrame *Game_GetViewFrame(int view, int loop, int frame) { view--; // convert to 0-based AssertFrame("Game.GetViewFrame", view, loop, frame); ScriptViewFrame *sdt = new ScriptViewFrame(view, loop, frame); ccRegisterManagedObject(sdt, sdt); return sdt; } int Game_DoOnceOnly(const char *token) { if (_GP(play).do_once_tokens.find(String::Wrapper(token)) != _GP(play).do_once_tokens.end()) return 0; _GP(play).do_once_tokens.insert(token); return 1; } void Game_ResetDoOnceOnly() { free_do_once_tokens(); } int Game_GetTextReadingSpeed() { return _GP(play).text_speed; } void Game_SetTextReadingSpeed(int newTextSpeed) { if (newTextSpeed < 1) quitprintf("!Game.TextReadingSpeed: %d is an invalid speed", newTextSpeed); _GP(play).text_speed = newTextSpeed; } int Game_GetMinimumTextDisplayTimeMs() { return _GP(play).text_min_display_time_ms; } void Game_SetMinimumTextDisplayTimeMs(int newTextMinTime) { _GP(play).text_min_display_time_ms = newTextMinTime; } int Game_GetIgnoreUserInputAfterTextTimeoutMs() { return _GP(play).ignore_user_input_after_text_timeout_ms; } void Game_SetIgnoreUserInputAfterTextTimeoutMs(int newValueMs) { _GP(play).ignore_user_input_after_text_timeout_ms = newValueMs; } const char *Game_GetFileName() { return CreateNewScriptString(_GP(ResPaths).GamePak.Name.GetCStr()); } const char *Game_GetName() { return CreateNewScriptString(_GP(play).game_name); } void Game_SetName(const char *newName) { _GP(play).game_name = newName; sys_window_set_title(_GP(play).game_name.GetCStr()); GUI::MarkSpecialLabelsForUpdate(kLabelMacro_Gamename); } int Game_GetSkippingCutscene() { if (_GP(play).fast_forward) { return 1; } return 0; } int Game_GetInSkippableCutscene() { if (_GP(play).in_cutscene) { return 1; } return 0; } int Game_GetColorFromRGB(int red, int grn, int blu) { if ((red < 0) || (red > 255) || (grn < 0) || (grn > 255) || (blu < 0) || (blu > 255)) quit("!GetColorFromRGB: colour values must be 0-255"); if (_GP(game).color_depth == 1) { return makecol8(red, grn, blu); } int agscolor = ((blu >> 3) & 0x1f); agscolor += ((grn >> 2) & 0x3f) << 5; agscolor += ((red >> 3) & 0x1f) << 11; return agscolor; } const char *Game_InputBox(const char *msg) { char buffer[STD_BUFFER_SIZE]; ShowInputBoxImpl(msg, buffer, STD_BUFFER_SIZE); return CreateNewScriptString(buffer); } const char *Game_GetLocationName(int x, int y) { char buffer[STD_BUFFER_SIZE]; GetLocationName(x, y, buffer); // fills up to MAX_MAXSTRLEN return CreateNewScriptString(buffer); } const char *Game_GetGlobalMessages(int index) { if ((index < 500) || (index >= MAXGLOBALMES + 500)) { return nullptr; } char buffer[STD_BUFFER_SIZE]; replace_tokens(get_translation(get_global_message(index)), buffer, STD_BUFFER_SIZE); return CreateNewScriptString(buffer); } int Game_GetSpeechFont() { return _GP(play).speech_font; } int Game_GetNormalFont() { return _GP(play).normal_font; } const char *Game_GetTranslationFilename() { char buffer[STD_BUFFER_SIZE]; GetTranslationName(buffer); // fills up to MAX_MAXSTRLEN return CreateNewScriptString(buffer); } int Game_ChangeTranslation(const char *newFilename) { if ((newFilename == nullptr) || (newFilename[0] == 0)) { // switch back to default translation close_translation(); _GP(usetup).translation = ""; GUI::MarkForTranslationUpdate(); return 1; } String oldTransFileName = get_translation_name(); if (!init_translation(newFilename, oldTransFileName)) return 0; // failed, kept previous translation _GP(usetup).translation = newFilename; GUI::MarkForTranslationUpdate(); return 1; } const char *Game_GetSpeechVoxFilename() { return CreateNewScriptString(get_voicepak_name().GetCStr()); } bool Game_ChangeSpeechVox(const char *newFilename) { _GP(play).voice_avail = init_voicepak(newFilename); if (!_GP(play).voice_avail) { // if failed (and was not default)- fallback to default if (strlen(newFilename) > 0) _GP(play).voice_avail = init_voicepak(); return false; } return true; } int Game_GetAudioClipCount() { return _GP(game).audioClips.size(); } ScriptAudioClip *Game_GetAudioClip(int index) { if (index < 0 || (size_t)index >= _GP(game).audioClips.size()) return nullptr; return &_GP(game).audioClips[index]; } ScriptCamera *Game_GetCamera() { return _GP(play).GetScriptCamera(0); } int Game_GetCameraCount() { return _GP(play).GetRoomCameraCount(); } ScriptCamera *Game_GetAnyCamera(int index) { return _GP(play).GetScriptCamera(index); } void Game_SimulateKeyPress(int key) { ags_simulate_keypress(static_cast(key), (_GP(game).options[OPT_KEYHANDLEAPI] == 0)); } int Game_BlockingWaitSkipped() { return _GP(play).GetWaitSkipResult(); } void Game_PrecacheSprite(int sprnum) { const auto tp_start = AGS_Clock::now(); _GP(spriteset).PrecacheSprite(sprnum); const auto tp_filedone = AGS_Clock::now(); const auto dur1 = ToMilliseconds(tp_filedone - tp_start); Debug::Printf("Precache sprite %d; file->mem = %lld ms", sprnum, dur1); } void Game_PrecacheView(int view, int first_loop, int last_loop) { precache_view(view - 1 /* to 0-based view index */, first_loop, last_loop, true); } //============================================================================= // save game functions void serialize_bitmap(const Shared::Bitmap *thispic, Stream *out) { if (thispic != nullptr) { out->WriteInt32(thispic->GetWidth()); out->WriteInt32(thispic->GetHeight()); out->WriteInt32(thispic->GetColorDepth()); for (int cc = 0; cc < thispic->GetHeight(); cc++) { switch (thispic->GetColorDepth()) { case 8: // CHECKME: originally, AGS does not use real BPP here, but simply divides color depth by 8; // therefore 15-bit bitmaps are saved only partially? is this a bug? or? case 15: out->WriteArray(&thispic->GetScanLine(cc)[0], thispic->GetWidth(), 1); break; case 16: out->WriteArrayOfInt16((const int16_t *)&thispic->GetScanLine(cc)[0], thispic->GetWidth()); break; case 32: out->WriteArrayOfInt32((const int32_t *)&thispic->GetScanLine(cc)[0], thispic->GetWidth()); break; } } } } // On Windows we could just use IIDFromString but this is _G(platform)-independent void convert_guid_from_text_to_binary(const char *guidText, unsigned char *buffer) { guidText++; // skip { for (int bytesDone = 0; bytesDone < 16; bytesDone++) { if (*guidText == '-') guidText++; char tempString[3]; tempString[0] = guidText[0]; tempString[1] = guidText[1]; tempString[2] = 0; uint thisByte = 0; sscanf(tempString, "%X", &thisByte); buffer[bytesDone] = thisByte; guidText += 2; } // Swap bytes to give correct GUID order unsigned char temp; temp = buffer[0]; buffer[0] = buffer[3]; buffer[3] = temp; temp = buffer[1]; buffer[1] = buffer[2]; buffer[2] = temp; temp = buffer[4]; buffer[4] = buffer[5]; buffer[5] = temp; temp = buffer[6]; buffer[6] = buffer[7]; buffer[7] = temp; } Bitmap *read_serialized_bitmap(Stream *in) { Bitmap *thispic; int picwid = in->ReadInt32(); int pichit = in->ReadInt32(); int piccoldep = in->ReadInt32(); thispic = BitmapHelper::CreateBitmap(picwid, pichit, piccoldep); if (thispic == nullptr) return nullptr; for (int vv = 0; vv < pichit; vv++) { switch (piccoldep) { case 8: // CHECKME: originally, AGS does not use real BPP here, but simply divides color depth by 8 case 15: in->ReadArray(thispic->GetScanLineForWriting(vv), picwid, 1); break; case 16: in->ReadArrayOfInt16((int16_t *)thispic->GetScanLineForWriting(vv), picwid); break; case 32: in->ReadArrayOfInt32((int32_t *)thispic->GetScanLineForWriting(vv), picwid); break; } } return thispic; } void skip_serialized_bitmap(Stream *in) { int picwid = in->ReadInt32(); int pichit = in->ReadInt32(); int piccoldep = in->ReadInt32(); // CHECKME: originally, AGS does not use real BPP here, but simply divides color depth by 8 int bpp = piccoldep / 8; in->Seek(picwid * pichit * bpp); } long write_screen_shot_for_vista(Stream *out, Bitmap *screenshot) { // Save the screenshot to a memory stream so we can access the raw data Common::MemoryWriteStreamDynamic bitmap(DisposeAfterUse::YES); screenshot->SaveToFile(bitmap, _G(palette)); update_polled_stuff(); // Write the bitmap to the output stream out->Write(bitmap.getData(), bitmap.size()); return bitmap.size(); } Bitmap *create_savegame_screenshot() { if ((_GP(play).screenshot_width < 16) || (_GP(play).screenshot_height < 16)) quit("!Invalid game.screenshot_width/height, must be from 16x16 to screen res"); // Render the view without any UI elements int old_flags = _G(debug_flags); _G(debug_flags) |= DBG_NOIFACE; construct_game_scene(true); render_to_screen(); _G(debug_flags) = old_flags; int usewid = data_to_game_coord(_GP(play).screenshot_width); int usehit = data_to_game_coord(_GP(play).screenshot_height); // NOTE: be aware that by the historical logic AGS makes a screenshot // of a "main viewport", that may be smaller in legacy "letterbox" mode. const Rect &viewport = _GP(play).GetMainViewport(); usewid = std::min(usewid, viewport.GetWidth()); usehit = std::min(usehit, viewport.GetHeight()); Bitmap *screenshot = CopyScreenIntoBitmap(usewid, usehit, &viewport); screenshot->GetAllegroBitmap()->makeOpaque(); // Restore original screen construct_game_scene(true); render_to_screen(); return screenshot; } void save_game(int slotn, const char *descript) { VALIDATE_STRING(descript); // dont allow save in rep_exec_always, because we dont save // the state of blocked scripts can_run_delayed_command(); if (_G(inside_script)) { snprintf(_G(curscript)->postScriptSaveSlotDescription[_G(curscript)->queue_action(ePSASaveGame, slotn, "SaveGameSlot")], MAX_QUEUED_ACTION_DESC, "%s", descript); return; } if (_G(platform)->GetDiskFreeSpaceMB(get_save_game_directory()) < 2) { Display("ERROR: There is not enough disk space free to save the game. Clear some disk space and try again."); return; } String nametouse = get_save_game_path(slotn); std::unique_ptr screenShot; // WORKAROUND: AGS originally only creates savegames if the game flags // that it supports it. But we want it all the time for ScummVM GMM, // unless explicitly disabled through gameflag if ((/*_GP(game).options[OPT_SAVESCREENSHOT] != 0*/ true) && _G(saveThumbnail) && slotn != 999) screenShot.reset(create_savegame_screenshot()); std::unique_ptr out(StartSavegame(nametouse, descript, screenShot.get())); if (out == nullptr) { Display("ERROR: Unable to open savegame file for writing!"); return; } // Actual dynamic game data is saved here SaveGameState(out.get()); // call "After Save" event callback run_on_event(GE_SAVE_GAME, RuntimeScriptValue().SetInt32(slotn)); if (screenShot != nullptr) { int screenShotOffset = out->GetPosition() - sizeof(RICH_GAME_MEDIA_HEADER); int screenShotSize = write_screen_shot_for_vista(out.get(), screenShot.get()); update_polled_stuff(); // TODO: probably unneeded out->Seek(12, kSeekBegin); out->WriteInt32(screenShotOffset); out->Seek(4); out->WriteInt32(screenShotSize); } } bool read_savedgame_description(const String &savedgame, String &description) { SavegameDescription desc; HSaveError err = OpenSavegame(savedgame, desc, kSvgDesc_UserText); if (!err) { Debug::Printf(kDbgMsg_Error, "Unable to read save's description.\n%s", err->FullMessage().GetCStr()); return false; } description = desc.UserText; return true; } std::unique_ptr read_savedgame_screenshot(const String &savedgame) { SavegameDescription desc; HSaveError err = OpenSavegame(savedgame, desc, kSvgDesc_UserImage); if (!err) { Debug::Printf(kDbgMsg_Error, "Unable to read save's screenshot.\n%s", err->FullMessage().GetCStr()); return nullptr; } if (desc.UserImage) { desc.UserImage.reset(PrepareSpriteForUse(desc.UserImage.release(), false)); return std::move(desc.UserImage); } return nullptr; } // Test if the game file contains expected GUID / legacy id bool test_game_guid(const String &filepath, const String &guid, int legacy_id) { std::unique_ptr amgr(new AssetManager()); if (amgr->AddLibrary(filepath) != kAssetNoError) return false; MainGameSource src; if (!OpenMainGameFileFromDefaultAsset(src, amgr.get())) return false; GameSetupStruct g; PreReadGameData(g, src.InputStream.get(), src.DataVersion); if (!guid.IsEmpty()) return guid.CompareNoCase(g.guid) == 0; return legacy_id == g.uniqueid; } static const SavegameDescription *loadDesc; static bool TestGame(const String &filepath) { return test_game_guid(filepath, loadDesc->GameGuid, loadDesc->LegacyID); } HSaveError load_game(const String &path, int slotNumber, bool &data_overwritten) { data_overwritten = false; _G(gameHasBeenRestored)++; _G(oldeip) = get_our_eip(); set_our_eip(2050); HSaveError err; SavegameSource src; SavegameDescription desc; err = OpenSavegame(path, src, desc, kSvgDesc_EnvInfo); // saved in incompatible environment if (!err) return err; // CHECKME: is this color depth test still essential? if yes, is there possible workaround? else if (desc.ColorDepth != _GP(game).GetColorDepth()) return new SavegameError(kSvgErr_DifferentColorDepth, String::FromFormat("Running: %d-bit, saved in: %d-bit.", _GP(game).GetColorDepth(), desc.ColorDepth)); // saved with different game file // if savegame is modern enough then test game GUIDs if (!desc.GameGuid.IsEmpty() || desc.LegacyID != 0) { if (desc.GameGuid.Compare(_GP(game).guid) != 0 && desc.LegacyID != _GP(game).uniqueid) { // Try to find wanted game's data using game id loadDesc = &desc; String gamefile = FindGameData(_GP(ResPaths).DataDir, TestGame); if (!gamefile.IsEmpty()) { RunAGSGame(gamefile.GetCStr(), 0, 0); _G(load_new_game_restore) = slotNumber; return HSaveError::None(); } return new SavegameError(kSvgErr_GameGuidMismatch); } } // if it's old then do the stupid old-style filename test // TODO: remove filename test after deprecating old saves else if (desc.MainDataFilename.Compare(_GP(ResPaths).GamePak.Name)) { String gamefile = Path::ConcatPaths(_GP(ResPaths).DataDir, desc.MainDataFilename); if (IsMainGameLibrary(gamefile)) { RunAGSGame(desc.MainDataFilename, 0, 0); _G(load_new_game_restore) = slotNumber; return HSaveError::None(); } // if it does not exist, continue loading savedgame in current game, and pray for the best Shared::Debug::Printf(kDbgMsg_Warn, "WARNING: the saved game '%s' references game file '%s' (title: '%s'), but it cannot be found in the current directory. Trying to restore in the running game instead.", path.GetCStr(), desc.MainDataFilename.GetCStr(), desc.GameTitle.GetCStr()); } // do the actual restore err = RestoreGameState(src.InputStream.get(), src.Version); data_overwritten = true; if (!err) return err; src.InputStream.reset(); set_our_eip(_G(oldeip)); // ensure input state is reset ags_clear_input_state(); // call "After Restore" event callback run_on_event(GE_RESTORE_GAME, RuntimeScriptValue().SetInt32(slotNumber)); return HSaveError::None(); } bool try_restore_save(int slot) { return try_restore_save(get_save_game_path(slot), slot); } bool try_restore_save(const Shared::String &path, int slot) { bool data_overwritten; Debug::Printf(kDbgMsg_Info, "Restoring saved game '%s'", path.GetCStr()); HSaveError err = load_game(path, slot, data_overwritten); if (!err) { String error = String::FromFormat("Unable to restore the saved game.\n%s", err->FullMessage().GetCStr()); Debug::Printf(kDbgMsg_Error, "%s", error.GetCStr()); // currently AGS cannot properly revert to stable state if some of the // game data was released or overwritten by the data from save file, // this is why we tell engine to shutdown if that happened. if (data_overwritten) quitprintf("%s", error.GetCStr()); else Display(error.GetCStr()); return false; } return true; } bool is_in_cutscene() { return _GP(play).in_cutscene > 0; } CutsceneSkipStyle get_cutscene_skipstyle() { return static_cast(_GP(play).in_cutscene); } void start_skipping_cutscene() { _GP(play).fast_forward = 1; // if a drop-down icon bar is up, remove it as it will pause the game if (_G(ifacepopped) >= 0) remove_popup_interface(_G(ifacepopped)); // if a text message is currently displayed, remove it if (_GP(play).text_overlay_on > 0) { remove_screen_overlay(_GP(play).text_overlay_on); _GP(play).SetWaitSkipResult(SKIP_AUTOTIMER); } } bool check_skip_cutscene_keypress(int kgn) { if (IsAGSServiceKey(static_cast(kgn))) return false; CutsceneSkipStyle skip = get_cutscene_skipstyle(); if (skip == eSkipSceneAnyKey || skip == eSkipSceneKeyMouse || (kgn == eAGSKeyCodeEscape && (skip == eSkipSceneEscOnly || skip == eSkipSceneEscOrRMB))) { start_skipping_cutscene(); return true; } return false; } bool check_skip_cutscene_mclick(int mbut) { CutsceneSkipStyle skip = get_cutscene_skipstyle(); if (skip == eSkipSceneMouse || skip == eSkipSceneKeyMouse || (mbut == kMouseRight && skip == eSkipSceneEscOrRMB)) { start_skipping_cutscene(); return true; } return false; } // Helper functions used by StartCutscene/EndCutscene, but also // by SkipUntilCharacterStops void initialize_skippable_cutscene() { _GP(play).end_cutscene_music = -1; } void stop_fast_forwarding() { // when the skipping of a cutscene comes to an end, update things _GP(play).fast_forward = 0; setpal(); if (_GP(play).end_cutscene_music >= 0) newmusic(_GP(play).end_cutscene_music); // Restore actual volume of sounds for (int aa = 0; aa < TOTAL_AUDIO_CHANNELS; aa++) { auto *ch = AudioChans::GetChannelIfPlaying(aa); if (ch) { ch->set_mute(false); } } update_music_volume(); } // allowHotspot0 defines whether Hotspot 0 returns LOCTYPE_HOTSPOT // or whether it returns 0 int __GetLocationType(int xxx, int yyy, int allowHotspot0) { _G(getloctype_index) = 0; // If it's not in ProcessClick, then return 0 when over a GUI if ((GetGUIAt(xxx, yyy) >= 0) && (_G(getloctype_throughgui) == 0)) return 0; _G(getloctype_throughgui) = 0; const int scrx = xxx; const int scry = yyy; VpPoint vpt = _GP(play).ScreenToRoomDivDown(xxx, yyy); if (vpt.second < 0) return 0; xxx = vpt.first.X; yyy = vpt.first.Y; if ((xxx >= _GP(thisroom).Width) || (xxx < 0) || (yyy < 0) || (yyy >= _GP(thisroom).Height)) return 0; // check characters, objects and walkbehinds, work out which is // foremost visible to the player int charat = is_pos_on_character(xxx, yyy); int hsat = get_hotspot_at(xxx, yyy); int objat = GetObjectIDAtScreen(scrx, scry); data_to_game_coords(&xxx, &yyy); int wbat = _GP(thisroom).WalkBehindMask->GetPixel(xxx, yyy); if (wbat <= 0) wbat = 0; else wbat = _G(croom)->walkbehind_base[wbat]; int winner = 0; // if it's an Ignore Walkbehinds object, then ignore the walkbehind if ((objat >= 0) && ((_G(objs)[objat].flags & OBJF_NOWALKBEHINDS) != 0)) wbat = 0; if ((charat >= 0) && ((_GP(game).chars[charat].flags & CHF_NOWALKBEHINDS) != 0)) wbat = 0; if ((charat >= 0) && (objat >= 0)) { if ((wbat > _G(obj_lowest_yp)) && (wbat > _G(char_lowest_yp))) winner = LOCTYPE_HOTSPOT; else if (_G(obj_lowest_yp) > _G(char_lowest_yp)) winner = LOCTYPE_OBJ; else winner = LOCTYPE_CHAR; } else if (charat >= 0) { if (wbat > _G(char_lowest_yp)) winner = LOCTYPE_HOTSPOT; else winner = LOCTYPE_CHAR; } else if (objat >= 0) { if (wbat > _G(obj_lowest_yp)) winner = LOCTYPE_HOTSPOT; else winner = LOCTYPE_OBJ; } if (winner == 0) { if (hsat >= 0) winner = LOCTYPE_HOTSPOT; } if ((winner == LOCTYPE_HOTSPOT) && (!allowHotspot0) && (hsat == 0)) winner = 0; if (winner == LOCTYPE_HOTSPOT) _G(getloctype_index) = hsat; else if (winner == LOCTYPE_CHAR) _G(getloctype_index) = charat; else if (winner == LOCTYPE_OBJ) _G(getloctype_index) = objat; return winner; } // Called whenever game loses input focus void display_switch_out() { Debug::Printf("Switching out from the game"); _G(switched_away) = true; ags_clear_input_state(); // Always unlock mouse when switching out from the game _GP(mouse).UnlockFromWindow(); } // Called when game looses input focus and must pause until focus is returned void display_switch_out_suspend() { Debug::Printf("Suspending the game on switch out"); _G(switching_away_from_game)++; _G(game_update_suspend)++; display_switch_out(); _G(platform)->PauseApplication(); // TODO: find out if anything has to be done here for SDL backend video_pause(); // Pause all the sounds for (int i = 0; i < TOTAL_AUDIO_CHANNELS; i++) { auto *ch = AudioChans::GetChannelIfPlaying(i); if (ch) { ch->pause(); } } _G(switching_away_from_game)--; } // Called whenever game gets input focus void display_switch_in() { Debug::Printf("Switching back into the game"); ags_clear_input_state(); // If auto lock option is set, lock mouse to the game window if (_GP(usetup).mouse_auto_lock && _GP(scsystem).windowed) _GP(mouse).TryLockToWindow(); _G(switched_away) = false; } // Called when game gets input focus and must resume after pause void display_switch_in_resume() { Debug::Printf("Resuming the game on switch in"); display_switch_in(); // Resume all the sounds for (int i = 0; i < TOTAL_AUDIO_CHANNELS; i++) { auto *ch = AudioChans::GetChannelIfPlaying(i); if (ch) { ch->resume(); } } video_resume(); // clear the screen if necessary if (_G(gfxDriver) && _G(gfxDriver)->UsesMemoryBackBuffer()) _G(gfxDriver)->ClearRectangle(0, 0, _GP(game).GetGameRes().Width - 1, _GP(game).GetGameRes().Height - 1, nullptr); // TODO: find out if anything has to be done here for SDL backend _G(platform)->ResumeApplication(); _G(game_update_suspend)--; } void replace_tokens(const char *srcmes, char *destm, size_t maxlen) { size_t indxdest = 0, indxsrc = 0; const char *srcp; char *destp; while (srcmes[indxsrc] != 0) { srcp = &srcmes[indxsrc]; destp = &destm[indxdest]; if ((strncmp(srcp, "@IN", 3) == 0) || (strncmp(srcp, "@GI", 3) == 0)) { int tokentype = 0; if (srcp[1] == 'I') tokentype = 1; else tokentype = 2; int inx = atoi(&srcp[3]); srcp++; indxsrc += 2; while (srcp[0] != '@') { if (srcp[0] == 0) quit("!Display: special token not terminated"); srcp++; indxsrc++; } char tval[10]; if (tokentype == 1) { if ((inx < 1) || (inx >= _GP(game).numinvitems)) quit("!Display: invalid inv item specified in @IN@"); snprintf(tval, sizeof(tval), "%d", _G(playerchar)->inv[inx]); } else { if ((inx < 0) || (inx >= MAXGSVALUES)) quit("!Display: invalid global int index speicifed in @GI@"); snprintf(tval, sizeof(tval), "%d", GetGlobalInt(inx)); } snprintf(destp, maxlen, "%s", tval); indxdest += strlen(tval); } else { destp[0] = srcp[0]; indxdest++; indxsrc++; } if (indxdest >= maxlen - 3) break; } destm[indxdest] = 0; } const char *get_global_message(int msnum) { if (_GP(game).messages[msnum - 500].IsEmpty()) return ""; return get_translation(_GP(game).messages[msnum - 500].GetCStr()); } void get_message_text(int msnum, char *buffer, char giveErr) { int maxlen = 9999; if (!giveErr) maxlen = MAX_MAXSTRLEN; if (msnum >= 500) { if ((msnum >= MAXGLOBALMES + 500) || (_GP(game).messages[msnum - 500].IsEmpty())) { if (giveErr) quit("!DisplayGlobalMessage: message does not exist"); buffer[0] = 0; return; } buffer[0] = 0; replace_tokens(get_translation(_GP(game).messages[msnum - 500].GetCStr()), buffer, maxlen); return; } else if (msnum < 0 || (size_t)msnum >= _GP(thisroom).MessageCount) { if (giveErr) quit("!DisplayMessage: Invalid message number to display"); buffer[0] = 0; return; } buffer[0] = 0; replace_tokens(get_translation(_GP(thisroom).Messages[msnum].GetCStr()), buffer, maxlen); } void game_sprite_updated(int sprnum, bool deleted) { // Notify draw system about dynamic sprite change notify_sprite_changed(sprnum, deleted); // GUI still have a special draw route, so cannot rely on object caches; // will have to do a per-GUI and per-control check. // TODO: devise a way to speed this iteration up, perhaps link GUI objects // to the sprite notification block somehow, etc...? // // gui backgrounds for (auto &gui : _GP(guis)) { if (gui.BgImage == sprnum) { gui.MarkChanged(); } } // gui buttons for (auto &but : _GP(guibuts)) { if (but.GetCurrentImage() == sprnum) { but.MarkChanged(); } } // gui sliders for (auto &slider : _GP(guislider)) { if ((slider.BgImage == sprnum) || (slider.HandleImage == sprnum)) { slider.MarkChanged(); } } } void precache_view(int view, int first_loop, int last_loop, bool with_sounds) { if (view < 0) return; if (first_loop > last_loop) return; first_loop = Math::Clamp(first_loop, 0, _GP(views)[view].numLoops - 1); last_loop = Math::Clamp(last_loop, 0, _GP(views)[view].numLoops - 1); // Record cache sizes and timestamps, for diagnostic purposes const size_t spcache_before = _GP(spriteset).GetCacheSize(); int total_frames = 0; int64_t dur_sp_load = 0; for (int i = first_loop; i <= last_loop; ++i) { for (int j = 0; j < _GP(views)[view].loops[i].numFrames; ++j, ++total_frames) { const auto &frame = _GP(views)[view].loops[i].frames[j]; const auto tp_detail1 = AGS_Clock::now(); _GP(spriteset).PrecacheSprite(frame.pic); const auto tp_detail2 = AGS_Clock::now(); dur_sp_load += ToMilliseconds(tp_detail2 - tp_detail1); } } // Print gathered time and size info size_t spcache_after = _GP(spriteset).GetCacheSize(); Debug::Printf("Precache view %d (loops %d-%d) with %d frames, total = %lld ms, average file->mem = %lld ms", view, first_loop, last_loop, total_frames, dur_sp_load, dur_sp_load / total_frames); Debug::Printf("\tSprite cache: %zu -> %zu KB", spcache_before / 1024u, spcache_after / 1024u); } //============================================================================= // // Script API Functions // //============================================================================= // int (int audioType); RuntimeScriptValue Sc_Game_IsAudioPlaying(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT(Game_IsAudioPlaying); } // void (int audioType, int volumeDrop) RuntimeScriptValue Sc_Game_SetAudioTypeSpeechVolumeDrop(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT2(Game_SetAudioTypeSpeechVolumeDrop); } // void (int audioType, int volume, int changeType) RuntimeScriptValue Sc_Game_SetAudioTypeVolume(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT3(Game_SetAudioTypeVolume); } // void (int audioType) RuntimeScriptValue Sc_Game_StopAudio(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(Game_StopAudio); } // int (const char *newFilename) RuntimeScriptValue Sc_Game_ChangeTranslation(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_POBJ(Game_ChangeTranslation, const char); } RuntimeScriptValue Sc_Game_ChangeSpeechVox(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_BOOL_POBJ(Game_ChangeSpeechVox, const char); } // int (const char *token) RuntimeScriptValue Sc_Game_DoOnceOnly(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_POBJ(Game_DoOnceOnly, const char); } RuntimeScriptValue Sc_Game_ResetDoOnceOnly(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID(Game_ResetDoOnceOnly); } // int (int red, int grn, int blu) RuntimeScriptValue Sc_Game_GetColorFromRGB(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT3(Game_GetColorFromRGB); } // int (int viewNumber, int loopNumber) RuntimeScriptValue Sc_Game_GetFrameCountForLoop(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT2(Game_GetFrameCountForLoop); } // const char* (int x, int y) RuntimeScriptValue Sc_Game_GetLocationName(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_PINT2(const char, _GP(myScriptStringImpl), Game_GetLocationName); } // int (int viewNumber) RuntimeScriptValue Sc_Game_GetLoopCountForView(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT(Game_GetLoopCountForView); } // int () RuntimeScriptValue Sc_Game_GetMODPattern(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetMODPattern); } // int (int viewNumber, int loopNumber) RuntimeScriptValue Sc_Game_GetRunNextSettingForLoop(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT2(Game_GetRunNextSettingForLoop); } // const char* (int slnum) RuntimeScriptValue Sc_Game_GetSaveSlotDescription(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetSaveSlotDescription); } // ScriptViewFrame* (int viewNumber, int loopNumber, int frame) RuntimeScriptValue Sc_Game_GetViewFrame(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJAUTO_PINT3(ScriptViewFrame, Game_GetViewFrame); } // const char* (const char *msg) RuntimeScriptValue Sc_Game_InputBox(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), Game_InputBox, const char); } // int (const char *newFolder) RuntimeScriptValue Sc_Game_SetSaveGameDirectory(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_POBJ(Game_SetSaveGameDirectory, const char); } // void (int evenAmbient); RuntimeScriptValue Sc_StopAllSounds(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(StopAllSounds); } // int () RuntimeScriptValue Sc_Game_GetCharacterCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetCharacterCount); } // int () RuntimeScriptValue Sc_Game_GetDialogCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetDialogCount); } // const char *() RuntimeScriptValue Sc_Game_GetFileName(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetFileName); } // int () RuntimeScriptValue Sc_Game_GetFontCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetFontCount); } // const char* (int index) RuntimeScriptValue Sc_Game_GetGlobalMessages(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetGlobalMessages); } // const char* (int index) RuntimeScriptValue Sc_Game_GetGlobalStrings(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Game_GetGlobalStrings); } // void (int index, char *newval); RuntimeScriptValue Sc_SetGlobalString(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT_POBJ(SetGlobalString, const char); } // int () RuntimeScriptValue Sc_Game_GetGUICount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetGUICount); } // int () RuntimeScriptValue Sc_Game_GetIgnoreUserInputAfterTextTimeoutMs(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetIgnoreUserInputAfterTextTimeoutMs); } // void (int newValueMs) RuntimeScriptValue Sc_Game_SetIgnoreUserInputAfterTextTimeoutMs(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(Game_SetIgnoreUserInputAfterTextTimeoutMs); } // int () RuntimeScriptValue Sc_Game_GetInSkippableCutscene(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetInSkippableCutscene); } // int () RuntimeScriptValue Sc_Game_GetInventoryItemCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetInventoryItemCount); } // int () RuntimeScriptValue Sc_Game_GetMinimumTextDisplayTimeMs(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetMinimumTextDisplayTimeMs); } // void (int newTextMinTime) RuntimeScriptValue Sc_Game_SetMinimumTextDisplayTimeMs(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(Game_SetMinimumTextDisplayTimeMs); } // int () RuntimeScriptValue Sc_Game_GetMouseCursorCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetMouseCursorCount); } // const char *() RuntimeScriptValue Sc_Game_GetName(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetName); } // void (const char *newName) RuntimeScriptValue Sc_Game_SetName(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_POBJ(Game_SetName, const char); } // int () RuntimeScriptValue Sc_Game_GetNormalFont(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetNormalFont); } // void (int fontnum); RuntimeScriptValue Sc_SetNormalFont(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(SetNormalFont); } // int () RuntimeScriptValue Sc_Game_GetSkippingCutscene(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetSkippingCutscene); } // int () RuntimeScriptValue Sc_Game_GetSpeechFont(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetSpeechFont); } // void (int fontnum); RuntimeScriptValue Sc_SetSpeechFont(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(SetSpeechFont); } // int (int spriteNum) RuntimeScriptValue Sc_Game_GetSpriteWidth(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT(Game_GetSpriteWidth); } // int (int spriteNum) RuntimeScriptValue Sc_Game_GetSpriteHeight(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_PINT(Game_GetSpriteHeight); } // int () RuntimeScriptValue Sc_Game_GetTextReadingSpeed(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetTextReadingSpeed); } // void (int newTextSpeed) RuntimeScriptValue Sc_Game_SetTextReadingSpeed(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(Game_SetTextReadingSpeed); } // const char* () RuntimeScriptValue Sc_Game_GetTranslationFilename(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetTranslationFilename); } RuntimeScriptValue Sc_Game_GetSpeechVoxFilename(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ(const char, _GP(myScriptStringImpl), Game_GetSpeechVoxFilename); } // int () RuntimeScriptValue Sc_Game_GetUseNativeCoordinates(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetUseNativeCoordinates); } // int () RuntimeScriptValue Sc_Game_GetViewCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetViewCount); } RuntimeScriptValue Sc_Game_GetAudioClipCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetAudioClipCount); } RuntimeScriptValue Sc_Game_GetAudioClip(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_PINT(ScriptAudioClip, _GP(ccDynamicAudioClip), Game_GetAudioClip); } RuntimeScriptValue Sc_Game_IsPluginLoaded(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_BOOL_POBJ(pl_is_plugin_loaded, const char); } RuntimeScriptValue Sc_Game_PlayVoiceClip(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_POBJ_PINT_PBOOL(ScriptAudioChannel, _GP(ccDynamicAudio), PlayVoiceClip, CharacterInfo); } RuntimeScriptValue Sc_Game_GetCamera(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJAUTO(ScriptCamera, Game_GetCamera); } RuntimeScriptValue Sc_Game_GetCameraCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_GetCameraCount); } RuntimeScriptValue Sc_Game_GetAnyCamera(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJAUTO_PINT(ScriptCamera, Game_GetAnyCamera); } RuntimeScriptValue Sc_Game_SimulateKeyPress(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(Game_SimulateKeyPress); } RuntimeScriptValue Sc_Game_BlockingWaitSkipped(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Game_BlockingWaitSkipped); } RuntimeScriptValue Sc_Game_PrecacheSprite(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT(Game_PrecacheSprite); } RuntimeScriptValue Sc_Game_PrecacheView(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT3(Game_PrecacheView); } void RegisterGameAPI() { ScFnRegister game_api[] = { {"Game::IsAudioPlaying^1", API_FN_PAIR(Game_IsAudioPlaying)}, {"Game::SetAudioTypeSpeechVolumeDrop^2", API_FN_PAIR(Game_SetAudioTypeSpeechVolumeDrop)}, {"Game::SetAudioTypeVolume^3", API_FN_PAIR(Game_SetAudioTypeVolume)}, {"Game::StopAudio^1", API_FN_PAIR(Game_StopAudio)}, {"Game::ChangeTranslation^1", API_FN_PAIR(Game_ChangeTranslation)}, {"Game::DoOnceOnly^1", API_FN_PAIR(Game_DoOnceOnly)}, {"Game::GetColorFromRGB^3", API_FN_PAIR(Game_GetColorFromRGB)}, {"Game::GetFrameCountForLoop^2", API_FN_PAIR(Game_GetFrameCountForLoop)}, {"Game::GetLocationName^2", API_FN_PAIR(Game_GetLocationName)}, {"Game::GetLoopCountForView^1", API_FN_PAIR(Game_GetLoopCountForView)}, {"Game::GetMODPattern^0", API_FN_PAIR(Game_GetMODPattern)}, {"Game::GetRunNextSettingForLoop^2", API_FN_PAIR(Game_GetRunNextSettingForLoop)}, {"Game::GetSaveSlotDescription^1", API_FN_PAIR(Game_GetSaveSlotDescription)}, {"Game::GetViewFrame^3", API_FN_PAIR(Game_GetViewFrame)}, {"Game::InputBox^1", API_FN_PAIR(Game_InputBox)}, {"Game::SetSaveGameDirectory^1", API_FN_PAIR(Game_SetSaveGameDirectory)}, {"Game::StopSound^1", API_FN_PAIR(StopAllSounds)}, {"Game::IsPluginLoaded", Sc_Game_IsPluginLoaded}, {"Game::ChangeSpeechVox", API_FN_PAIR(Game_ChangeSpeechVox)}, {"Game::PlayVoiceClip", Sc_Game_PlayVoiceClip}, {"Game::SimulateKeyPress", API_FN_PAIR(Game_SimulateKeyPress)}, {"Game::ResetDoOnceOnly", API_FN_PAIR(Game_ResetDoOnceOnly)}, {"Game::PrecacheSprite", API_FN_PAIR(Game_PrecacheSprite)}, {"Game::PrecacheView", API_FN_PAIR(Game_PrecacheView)}, {"Game::get_CharacterCount", API_FN_PAIR(Game_GetCharacterCount)}, {"Game::get_DialogCount", API_FN_PAIR(Game_GetDialogCount)}, {"Game::get_FileName", API_FN_PAIR(Game_GetFileName)}, {"Game::get_FontCount", API_FN_PAIR(Game_GetFontCount)}, {"Game::geti_GlobalMessages", API_FN_PAIR(Game_GetGlobalMessages)}, {"Game::geti_GlobalStrings", API_FN_PAIR(Game_GetGlobalStrings)}, {"Game::seti_GlobalStrings", API_FN_PAIR(SetGlobalString)}, {"Game::get_GUICount", API_FN_PAIR(Game_GetGUICount)}, {"Game::get_IgnoreUserInputAfterTextTimeoutMs", API_FN_PAIR(Game_GetIgnoreUserInputAfterTextTimeoutMs)}, {"Game::set_IgnoreUserInputAfterTextTimeoutMs", API_FN_PAIR(Game_SetIgnoreUserInputAfterTextTimeoutMs)}, {"Game::get_InSkippableCutscene", API_FN_PAIR(Game_GetInSkippableCutscene)}, {"Game::get_InventoryItemCount", API_FN_PAIR(Game_GetInventoryItemCount)}, {"Game::get_MinimumTextDisplayTimeMs", API_FN_PAIR(Game_GetMinimumTextDisplayTimeMs)}, {"Game::set_MinimumTextDisplayTimeMs", API_FN_PAIR(Game_SetMinimumTextDisplayTimeMs)}, {"Game::get_MouseCursorCount", API_FN_PAIR(Game_GetMouseCursorCount)}, {"Game::get_Name", API_FN_PAIR(Game_GetName)}, {"Game::set_Name", API_FN_PAIR(Game_SetName)}, {"Game::get_NormalFont", API_FN_PAIR(Game_GetNormalFont)}, {"Game::set_NormalFont", API_FN_PAIR(SetNormalFont)}, {"Game::get_SkippingCutscene", API_FN_PAIR(Game_GetSkippingCutscene)}, {"Game::get_SpeechFont", API_FN_PAIR(Game_GetSpeechFont)}, {"Game::set_SpeechFont", API_FN_PAIR(SetSpeechFont)}, {"Game::geti_SpriteWidth", API_FN_PAIR(Game_GetSpriteWidth)}, {"Game::geti_SpriteHeight", API_FN_PAIR(Game_GetSpriteHeight)}, {"Game::get_TextReadingSpeed", API_FN_PAIR(Game_GetTextReadingSpeed)}, {"Game::set_TextReadingSpeed", API_FN_PAIR(Game_SetTextReadingSpeed)}, {"Game::get_TranslationFilename", API_FN_PAIR(Game_GetTranslationFilename)}, {"Game::get_UseNativeCoordinates", API_FN_PAIR(Game_GetUseNativeCoordinates)}, {"Game::get_ViewCount", API_FN_PAIR(Game_GetViewCount)}, {"Game::get_AudioClipCount", API_FN_PAIR(Game_GetAudioClipCount)}, {"Game::geti_AudioClips", API_FN_PAIR(Game_GetAudioClip)}, {"Game::get_BlockingWaitSkipped", API_FN_PAIR(Game_BlockingWaitSkipped)}, {"Game::get_SpeechVoxFilename", API_FN_PAIR(Game_GetSpeechVoxFilename)}, {"Game::get_Camera", API_FN_PAIR(Game_GetCamera)}, {"Game::get_CameraCount", API_FN_PAIR(Game_GetCameraCount)}, {"Game::geti_Cameras", API_FN_PAIR(Game_GetAnyCamera)}, }; ccAddExternalFunctions361(game_api); } void RegisterStaticObjects() { ccAddExternalScriptObject("game", &_GP(play), &_GP(GameStaticManager)); ccAddExternalScriptObject("gs_globals", &_GP(play).globalvars[0], &_GP(GlobalStaticManager)); ccAddExternalScriptObject("mouse", &_GP(scmouse), &_GP(scmouse)); ccAddExternalScriptObject("palette", &_G(palette)[0], &_GP(GlobalStaticManager)); ccAddExternalScriptObject("system", &_GP(scsystem), &_GP(scsystem)); ccAddExternalScriptObject("savegameindex", &_GP(play).filenumbers[0], &_GP(GlobalStaticManager)); } } // namespace AGS3