/* 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 "ags/shared/core/platform.h" #include "ags/shared/util/string_utils.h" //strlwr() #include "ags/shared/ac/common.h" #include "ags/engine/ac/character.h" #include "ags/engine/ac/character_extras.h" #include "ags/engine/ac/draw.h" #include "ags/engine/ac/event.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/global_audio.h" #include "ags/engine/ac/global_character.h" #include "ags/engine/ac/global_game.h" #include "ags/engine/ac/global_object.h" #include "ags/engine/ac/global_translation.h" #include "ags/engine/ac/move_list.h" #include "ags/engine/ac/mouse.h" #include "ags/engine/ac/overlay.h" #include "ags/engine/ac/properties.h" #include "ags/engine/ac/region.h" #include "ags/engine/ac/sys_events.h" #include "ags/engine/ac/room.h" #include "ags/engine/ac/room_object.h" #include "ags/engine/ac/room_status.h" #include "ags/engine/ac/screen.h" #include "ags/engine/ac/string.h" #include "ags/engine/ac/system.h" #include "ags/engine/ac/walkable_area.h" #include "ags/engine/ac/walk_behind.h" #include "ags/engine/ac/dynobj/script_object.h" #include "ags/engine/ac/dynobj/script_hotspot.h" #include "ags/engine/ac/dynobj/dynobj_manager.h" #include "ags/shared/gui/gui_main.h" #include "ags/engine/script/cc_instance.h" #include "ags/engine/debugging/debug_log.h" #include "ags/engine/debugging/debugger.h" #include "ags/shared/debugging/out.h" #include "ags/shared/game/room_version.h" #include "ags/engine/platform/base/ags_platform_driver.h" #include "ags/plugins/ags_plugin_evts.h" #include "ags/plugins/plugin_engine.h" #include "ags/shared/script/cc_common.h" #include "ags/engine/script/script.h" #include "ags/engine/script/script_runtime.h" #include "ags/shared/ac/sprite_cache.h" #include "ags/shared/util/stream.h" #include "ags/engine/gfx/graphics_driver.h" #include "ags/shared/core/asset_manager.h" #include "ags/engine/ac/dynobj/all_dynamic_classes.h" #include "ags/shared/gfx/bitmap.h" #include "ags/engine/gfx/gfxfilter.h" #include "ags/shared/util/math.h" #include "ags/engine/media/audio/audio_system.h" #include "ags/engine/main/game_run.h" #include "ags/shared/debugging/out.h" #include "ags/engine/script/script_api.h" #include "ags/engine/script/script_runtime.h" #include "ags/engine/ac/dynobj/script_string.h" #include "ags/globals.h" namespace AGS3 { using namespace AGS::Shared; using namespace AGS::Engine; ScriptDrawingSurface *Room_GetDrawingSurfaceForBackground(int backgroundNumber) { if (_G(displayed_room) < 0) quit("!Room.GetDrawingSurfaceForBackground: no room is currently loaded"); if (backgroundNumber == SCR_NO_VALUE) { backgroundNumber = _GP(play).bg_frame; } if ((backgroundNumber < 0) || ((size_t)backgroundNumber >= _GP(thisroom).BgFrameCount)) quit("!Room.GetDrawingSurfaceForBackground: invalid background number specified"); ScriptDrawingSurface *surface = new ScriptDrawingSurface(); surface->roomBackgroundNumber = backgroundNumber; ccRegisterManagedObject(surface, surface); return surface; } ScriptDrawingSurface *Room_GetDrawingSurfaceForMask(RoomAreaMask mask) { if (_G(displayed_room) < 0) quit("!Room_GetDrawingSurfaceForMask: no room is currently loaded"); ScriptDrawingSurface *surface = new ScriptDrawingSurface(); surface->roomMaskType = mask; ccRegisterManagedObject(surface, surface); return surface; } int Room_GetObjectCount() { return _G(croom)->numobj; } int Room_GetWidth() { return _GP(thisroom).Width; } int Room_GetHeight() { return _GP(thisroom).Height; } int Room_GetColorDepth() { return _GP(thisroom).BgFrames[0].Graphic->GetColorDepth(); } int Room_GetLeftEdge() { return _GP(thisroom).Edges.Left; } int Room_GetRightEdge() { return _GP(thisroom).Edges.Right; } int Room_GetTopEdge() { return _GP(thisroom).Edges.Top; } int Room_GetBottomEdge() { return _GP(thisroom).Edges.Bottom; } int Room_GetMusicOnLoad() { return _GP(thisroom).Options.StartupMusic; } int Room_GetProperty(const char *property) { return get_int_property(_GP(thisroom).Properties, _G(croom)->roomProps, property); } const char *Room_GetTextProperty(const char *property) { return get_text_property_dynamic_string(_GP(thisroom).Properties, _G(croom)->roomProps, property); } bool Room_SetProperty(const char *property, int value) { return set_int_property(_G(croom)->roomProps, property, value); } bool Room_SetTextProperty(const char *property, const char *value) { return set_text_property(_G(croom)->roomProps, property, value); } const char *Room_GetMessages(int index) { if ((index < 0) || ((size_t)index >= _GP(thisroom).MessageCount)) { return nullptr; } char buffer[STD_BUFFER_SIZE]; replace_tokens(get_translation(_GP(thisroom).Messages[index].GetCStr()), buffer, STD_BUFFER_SIZE); return CreateNewScriptString(buffer); } bool Room_Exists(int room) { String room_filename; room_filename.Format("room%d.crm", room); return _GP(AssetMgr)->DoesAssetExist(room_filename); } ScriptDrawingSurface *GetDrawingSurfaceForWalkableArea() { return Room_GetDrawingSurfaceForMask(kRoomAreaWalkable); } ScriptDrawingSurface *GetDrawingSurfaceForWalkbehind() { return Room_GetDrawingSurfaceForMask(kRoomAreaWalkBehind); } ScriptDrawingSurface *Hotspot_GetDrawingSurface() { return Room_GetDrawingSurfaceForMask(kRoomAreaHotspot); } ScriptDrawingSurface *Region_GetDrawingSurface() { return Room_GetDrawingSurfaceForMask(kRoomAreaRegion); } //============================================================================= // Makes sure that room background and walk-behind mask are matching room size // in game resolution coordinates; in other words makes graphics appropriate // for display in the game. void convert_room_background_to_game_res() { if (!_GP(game).AllowRelativeRes() || !_GP(thisroom).IsRelativeRes()) return; const int bkg_width = data_to_game_coord(_GP(thisroom).Width); const int bkg_height = data_to_game_coord(_GP(thisroom).Height); for (size_t i = 0; i < _GP(thisroom).BgFrameCount; ++i) _GP(thisroom).BgFrames[i].Graphic = FixBitmap(_GP(thisroom).BgFrames[i].Graphic, bkg_width, bkg_height); // Fix masks to match resized room background // Walk-behind is always 1:1 with room background size _GP(thisroom).WalkBehindMask = FixBitmap(_GP(thisroom).WalkBehindMask, bkg_width, bkg_height); // For the rest we keep the masks at original res, but update the MaskResolution, // as it must correspond to the runtime data->game coordinate conversion _GP(thisroom).MaskResolution = data_to_game_coord(_GP(thisroom).MaskResolution); } void save_room_data_segment() { _G(croom)->FreeScriptData(); _G(croom)->tsdatasize = _G(roominst)->globaldatasize; if (_G(croom)->tsdatasize > 0) { _G(croom)->tsdata.resize(_G(croom)->tsdatasize); memcpy(_G(croom)->tsdata.data(), &_G(roominst)->globaldata[0], _G(croom)->tsdatasize); } } void unload_old_room() { // if switching games on restore, don't do this if (_G(displayed_room) < 0) return; current_fade_out_effect(); // room unloaded callback run_room_event(EVROM_AFTERFADEOUT); // global room unloaded event run_on_event(GE_LEAVE_ROOM_AFTERFADE, RuntimeScriptValue().SetInt32(_G(displayed_room))); debug_script_log("Unloading room %d", _G(displayed_room)); dispose_room_drawdata(); for (uint32_t ff = 0; ff < _G(croom)->numobj; ff++) _G(objs)[ff].moving = 0; if (!_GP(play).ambient_sounds_persist) { for (int ff = NUM_SPEECH_CHANS; ff < _GP(game).numGameChannels; ff++) StopAmbientSound(ff); } cancel_all_scripts(); _GP(events).clear(); // cancel any pending room events if (_G(roomBackgroundBmp) != nullptr) { _G(gfxDriver)->DestroyDDB(_G(roomBackgroundBmp)); _G(roomBackgroundBmp) = nullptr; } if (_G(croom) == nullptr) ; else if (_G(roominst) != nullptr) { save_room_data_segment(); FreeRoomScriptInstance(); } else _G(croom)->tsdatasize = 0; memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS); _GP(play).bg_frame = 0; _GP(play).bg_frame_locked = 0; remove_all_overlays(); _G(raw_saved_screen).reset(); for (int ff = 0; ff < MAX_ROOM_BGFRAMES; ff++) _GP(play).raw_modified[ff] = 0; for (size_t i = 0; i < _GP(thisroom).LocalVariables.size() && i < MAX_GLOBAL_VARIABLES; ++i) _G(croom)->interactionVariableValues[i] = _GP(thisroom).LocalVariables[i].Value; // ensure that any half-moves (eg. with scaled movement) are stopped for (int ff = 0; ff < _GP(game).numcharacters; ff++) { _GP(charextra)[ff].xwas = INVALID_X; } _GP(play).swap_portrait_lastchar = -1; _GP(play).swap_portrait_lastlastchar = -1; for (uint32_t ff = 0; ff < _G(croom)->numobj; ff++) { // un-export the object's script object if (_GP(thisroom).Objects[ff].ScriptName.IsEmpty()) continue; ccRemoveExternalSymbol(_GP(thisroom).Objects[ff].ScriptName); } for (int ff = 0; ff < MAX_ROOM_HOTSPOTS; ff++) { if (_GP(thisroom).Hotspots[ff].ScriptName.IsEmpty()) continue; ccRemoveExternalSymbol(_GP(thisroom).Hotspots[ff].ScriptName); } croom_ptr_clear(); // clear the draw caches to save memory, since many of the the involved // objects probably aren't on the new screen clear_drawobj_cache(); // if Hide Player Character was ticked, restore it to visible if (_GP(play).temporarily_turned_off_character >= 0) { _GP(game).chars[_GP(play).temporarily_turned_off_character].on = 1; _GP(play).temporarily_turned_off_character = -1; } } // Convert all room objects to the data resolution (only if it's different from game resolution). // TODO: merge this into UpdateRoomData? or this is required for engine only? void convert_room_coordinates_to_data_res(RoomStruct *rstruc) { if (_GP(game).GetDataUpscaleMult() == 1) return; const int mul = _GP(game).GetDataUpscaleMult(); for (auto &obj : rstruc->Objects) { obj.X /= mul; obj.Y /= mul; if (obj.Baseline > 0) { obj.Baseline /= mul; } } for (size_t i = 0; i < rstruc->HotspotCount; ++i) { rstruc->Hotspots[i].WalkTo.X /= mul; rstruc->Hotspots[i].WalkTo.Y /= mul; } for (size_t i = 0; i < rstruc->WalkBehindCount; ++i) { rstruc->WalkBehinds[i].Baseline /= mul; } rstruc->Edges.Left /= mul; rstruc->Edges.Top /= mul; rstruc->Edges.Bottom /= mul; rstruc->Edges.Right /= mul; rstruc->Width /= mul; rstruc->Height /= mul; } void update_letterbox_mode() { const Size real_room_sz = Size(data_to_game_coord(_GP(thisroom).Width), data_to_game_coord(_GP(thisroom).Height)); const Rect game_frame = RectWH(_GP(game).GetGameRes()); Rect new_main_view = game_frame; // In the original engine the letterbox feature only allowed viewports of // either 200 or 240 (400 and 480) pixels, if the room height was equal or greater than 200 (400). // Also, the UI viewport should be matching room viewport in that case. // NOTE: if "OPT_LETTERBOX" is false, altsize.Height = size.Height always. const int viewport_height = real_room_sz.Height < _GP(game).GetLetterboxSize().Height ? real_room_sz.Height : (real_room_sz.Height >= _GP(game).GetLetterboxSize().Height && real_room_sz.Height < _GP(game).GetGameRes().Height) ? _GP(game).GetLetterboxSize().Height : _GP(game).GetGameRes().Height; new_main_view.SetHeight(viewport_height); _GP(play).SetMainViewport(CenterInRect(game_frame, new_main_view)); _GP(play).SetUIViewport(new_main_view); on_mainviewport_changed(); } // Automatically reset primary room viewport and camera to match the new room size static void adjust_viewport_to_room() { const Size real_room_sz = Size(data_to_game_coord(_GP(thisroom).Width), data_to_game_coord(_GP(thisroom).Height)); const Rect main_view = _GP(play).GetMainViewport(); Rect new_room_view = RectWH(Size::Clamp(real_room_sz, Size(1, 1), main_view.GetSize())); auto view = _GP(play).GetRoomViewport(0); view->SetRect(new_room_view); auto cam = view->GetCamera(); if (cam) { cam->SetSize(new_room_view.GetSize()); cam->SetAt(0, 0); cam->Release(); } } // Run through all viewports and cameras to make sure they can work in new room's bounds static void update_all_viewcams_with_newroom() { for (int i = 0; i < _GP(play).GetRoomCameraCount(); ++i) { auto cam = _GP(play).GetRoomCamera(i); const Rect old_pos = cam->GetRect(); cam->SetSize(old_pos.GetSize()); cam->SetAt(old_pos.Left, old_pos.Top); } } // Looks up for the room script available as a separate asset. // This is optional, so no error is raised if one is not found. // If found however, it will replace room script if one had been loaded // from the room file itself. HError LoadRoomScript(RoomStruct *room, int newnum) { String filename = String::FromFormat("room%d.o", newnum); std::unique_ptr in(_GP(AssetMgr)->OpenAsset(filename)); if (in) { PScript script(ccScript::CreateFromStream(in.get())); if (!script) return new Error(String::FromFormat( "Failed to load a script module: %s", filename.GetCStr()), cc_get_error().ErrorString); room->CompiledScript = script; } return HError::None(); } static void reset_temp_room() { _GP(troom) = RoomStatus(); } // forchar = playerchar on NewRoom, or NULL if restore saved game void load_new_room(int newnum, CharacterInfo *forchar) { debug_script_log("Loading room %d", newnum); String room_filename; _G(done_es_error) = 0; _GP(play).room_changes ++; // TODO: find out why do we need to temporarily lower color depth to 8-bit. // Or do we? There's a serious usability problem in this: if any bitmap is // created meanwhile it will have this color depth by default, which may // lead to unexpected errors. set_color_depth(8); _G(displayed_room) = newnum; room_filename.Format("room%d.crm", newnum); if (newnum == 0) { // support both room0.crm and intro.crm // 2.70: Renamed intro.crm to room0.crm, to stop it causing confusion if ((_G(loaded_game_file_version) < kGameVersion_270 && _GP(AssetMgr)->DoesAssetExist("intro.crm")) || (_G(loaded_game_file_version) >= kGameVersion_270 && !_GP(AssetMgr)->DoesAssetExist(room_filename))) { room_filename = "intro.crm"; } } // load the room from disk set_our_eip(200); _GP(thisroom).GameID = NO_GAME_ID_IN_ROOM_FILE; load_room(room_filename, &_GP(thisroom), _GP(game).IsLegacyHiRes(), _GP(game).SpriteInfos); if ((_GP(thisroom).GameID != NO_GAME_ID_IN_ROOM_FILE) && (_GP(thisroom).GameID != _GP(game).uniqueid)) { quitprintf("!Unable to load '%s'. This room file is assigned to a different game.", room_filename.GetCStr()); } HError err = LoadRoomScript(&_GP(thisroom), newnum); if (!err) quitprintf("!Unable to load '%s'. Error: %s", room_filename.GetCStr(), err->FullMessage().GetCStr()); convert_room_coordinates_to_data_res(&_GP(thisroom)); set_our_eip(201); _GP(play).room_width = _GP(thisroom).Width; _GP(play).room_height = _GP(thisroom).Height; _GP(play).anim_background_speed = _GP(thisroom).BgAnimSpeed; _GP(play).bg_anim_delay = _GP(play).anim_background_speed; // Fixup the frame index, in case the new room does not have enough background frames if (_GP(play).bg_frame < 0 || static_cast(_GP(play).bg_frame) >= _GP(thisroom).BgFrameCount) _GP(play).bg_frame = 0; // do the palette for (size_t cc = 0; cc < 256; cc++) { if (_GP(game).paluses[cc] == PAL_BACKGROUND) _G(palette)[cc] = _GP(thisroom).Palette[cc]; else { // copy the gamewide colours into the room palette for (size_t i = 0; i < _GP(thisroom).BgFrameCount; ++i) _GP(thisroom).BgFrames[i].Palette[cc] = _G(palette)[cc]; } } for (size_t i = 0; i < _GP(thisroom).BgFrameCount; ++i) { _GP(thisroom).BgFrames[i].Graphic = PrepareSpriteForUse(_GP(thisroom).BgFrames[i].Graphic, false); } set_our_eip(202); // Update game viewports if (_GP(game).IsLegacyLetterbox()) update_letterbox_mode(); SetMouseBounds(0, 0, 0, 0); set_our_eip(203); _G(in_new_room) = 1; set_color_depth(_GP(game).GetColorDepth()); // Make sure the room gfx and masks are matching game's native res convert_room_background_to_game_res(); // walkable_areas_temp is used by the pathfinder to generate a // copy of the walkable areas - allocate it here to save time later delete _G(walkable_areas_temp); _G(walkable_areas_temp) = BitmapHelper::CreateBitmap(_GP(thisroom).WalkAreaMask->GetWidth(), _GP(thisroom).WalkAreaMask->GetHeight(), 8); // Make a backup copy of the walkable areas prior to // any RemoveWalkableArea commands delete _G(walkareabackup); // copy the walls screen _G(walkareabackup) = BitmapHelper::CreateBitmapCopy(_GP(thisroom).WalkAreaMask.get()); set_our_eip(204); redo_walkable_areas(); walkbehinds_recalc(); set_our_eip(205); // setup objects if (forchar != nullptr) { // if not restoring a game, always reset this room reset_temp_room(); } if ((newnum >= 0) && (newnum < MAX_ROOMS)) _G(croom) = getRoomStatus(newnum); else _G(croom) = &_GP(troom); // Decide what to do if we have been or not in this room before if (_G(croom)->beenhere > 0) { // if we've been here before, save the Times Run information // since we will overwrite the actual NewInteraction structs // (cos they have pointers and this might have been loaded from // a save game) if (_GP(thisroom).EventHandlers == nullptr) { // legacy interactions _GP(thisroom).Interaction->CopyTimesRun(_G(croom)->intrRoom); for (int cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) _GP(thisroom).Hotspots[cc].Interaction->CopyTimesRun(_G(croom)->intrHotspot[cc]); for (size_t cc = 0; cc < _GP(thisroom).Objects.size(); cc++) _GP(thisroom).Objects[cc].Interaction->CopyTimesRun(_G(croom)->intrObject[cc]); for (int cc = 0; cc < MAX_ROOM_REGIONS; cc++) _GP(thisroom).Regions[cc].Interaction->CopyTimesRun(_G(croom)->intrRegion[cc]); } for (size_t i = 0; i < _GP(thisroom).LocalVariables.size() && i < (size_t)MAX_GLOBAL_VARIABLES; ++i) _GP(thisroom).LocalVariables[i].Value = _G(croom)->interactionVariableValues[i]; // Always copy object and hotspot names for < 3.6.0 games, because they were not settable if ((_G(loaded_game_file_version) < kGameVersion_360_16) || (_G(croom)->contentFormat < kRoomStatSvgVersion_36025)) { for (size_t cc = 0; cc < _GP(thisroom).Objects.size(); ++cc) _G(croom)->obj[cc].name = _GP(thisroom).Objects[cc].Name; for (int cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) _G(croom)->hotspot[cc].Name = _GP(thisroom).Hotspots[cc].Name; } } else { // If we have not been in this room before, then copy necessary fields from _GP(thisroom) _G(croom)->numobj = _GP(thisroom).Objects.size(); _G(croom)->tsdatasize = 0; _G(croom)->obj.resize(_G(croom)->numobj); _G(croom)->objProps.resize(_G(croom)->numobj); _G(croom)->intrObject.resize(_G(croom)->numobj); for (size_t cc = 0; cc < _G(croom)->numobj; cc++) { const auto &trobj = _GP(thisroom).Objects[cc]; auto &crobj = _G(croom)->obj[cc]; crobj.x = trobj.X; crobj.y = trobj.Y; crobj.num = Math::InRangeOrDef(trobj.Sprite, 0); crobj.on = trobj.IsOn; crobj.view = RoomObject::NoView; crobj.loop = 0; crobj.frame = 0; crobj.wait = 0; crobj.transparent = 0; crobj.moving = -1; crobj.flags = trobj.Flags; crobj.baseline = -1; crobj.zoom = 100; crobj.last_width = 0; crobj.last_height = 0; crobj.blocking_width = 0; crobj.blocking_height = 0; crobj.name = trobj.Name; if (trobj.Baseline >= 0) crobj.baseline = trobj.Baseline; if (trobj.Sprite > UINT16_MAX) debug_script_warn("Warning: object's (id %d) sprite %d outside of internal range (%d), reset to 0", cc, trobj.Sprite, UINT16_MAX); } for (size_t i = 0; i < (size_t)MAX_WALK_BEHINDS; ++i) _G(croom)->walkbehind_base[i] = _GP(thisroom).WalkBehinds[i].Baseline; for (int cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) { _G(croom)->hotspot[cc].Enabled = true; _G(croom)->hotspot[cc].Name = _GP(thisroom).Hotspots[cc].Name; } for (int cc = 0; cc < MAX_ROOM_REGIONS; cc++) { _G(croom)->region_enabled[cc] = 1; } #if defined (OBSOLETE) for (uint cc = 0; cc < MAX_LEGACY_ROOM_FLAGS; cc++) _G(croom)->flagstates[cc] = 0; // we copy these structs for the Score column to work _G(croom)->misccond = _GP(thisroom).misccond; for (uint cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) _G(croom)->hscond[cc] = _GP(thisroom).hscond[cc]; for (uint cc = 0; cc < MAX_ROOM_OBJECTS; cc++) _G(croom)->objcond[cc] = _GP(thisroom).objcond[cc]; #endif _G(croom)->beenhere = 1; _G(in_new_room) = 2; } // Reset contentFormat hint to avoid doing fixups later _G(croom)->contentFormat = kRoomStatSvgVersion_Current; if (_GP(thisroom).EventHandlers == nullptr) { // legacy interactions // copy interactions from room file into our temporary struct _G(croom)->intrRoom = *_GP(thisroom).Interaction; for (int cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) _G(croom)->intrHotspot[cc] = *_GP(thisroom).Hotspots[cc].Interaction; for (size_t cc = 0; cc < _GP(thisroom).Objects.size(); cc++) _G(croom)->intrObject[cc] = *_GP(thisroom).Objects[cc].Interaction; for (int cc = 0; cc < MAX_ROOM_REGIONS; cc++) _G(croom)->intrRegion[cc] = *_GP(thisroom).Regions[cc].Interaction; } _G(objs) = _G(croom)->obj.size() > 0 ? &_G(croom)->obj[0] : nullptr; for (size_t cc = 0; cc < _G(croom)->numobj; cc++) { // export the object's script object if (_GP(thisroom).Objects[cc].ScriptName.IsEmpty()) continue; ccAddExternalScriptObject(_GP(thisroom).Objects[cc].ScriptName, &_G(scrObj)[cc], &_GP(ccDynamicObject)); } for (int cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) { if (_GP(thisroom).Hotspots[cc].ScriptName.IsEmpty()) continue; ccAddExternalScriptObject(_GP(thisroom).Hotspots[cc].ScriptName, &_G(scrHotspot)[cc], &_GP(ccDynamicHotspot)); } set_our_eip(210); if (IS_ANTIALIAS_SPRITES) { // sometimes the palette has corrupt entries, which crash // the create_rgb_table call // so, fix them for (int ff = 0; ff < 256; ff++) { if (_G(palette)[ff].r > 63) _G(palette)[ff].r = 63; if (_G(palette)[ff].g > 63) _G(palette)[ff].g = 63; if (_G(palette)[ff].b > 63) _G(palette)[ff].b = 63; } create_rgb_table(&_GP(rgb_table), _G(palette), nullptr); _G(rgb_map) = &_GP(rgb_table); } set_our_eip(211); if (forchar != nullptr) { // if it's not a Restore Game // if a following character is still waiting to come into the // previous room, force it out so that the timer resets for (int ff = 0; ff < _GP(game).numcharacters; ff++) { if ((_GP(game).chars[ff].following >= 0) && (_GP(game).chars[ff].room < 0)) { if ((_GP(game).chars[ff].following == _GP(game).playercharacter) && (forchar->prevroom == newnum)) // the player went back to the previous room, so make sure // the following character is still there _GP(game).chars[ff].room = newnum; else _GP(game).chars[ff].room = _GP(game).chars[_GP(game).chars[ff].following].room; } } forchar->prevroom = forchar->room; forchar->room = newnum; // only stop moving if it's a new room, not a restore game for (int cc = 0; cc < _GP(game).numcharacters; cc++) StopMoving(cc); } _G(roominst).reset(); if (_G(debug_flags) & DBG_NOSCRIPT) ; else if (_GP(thisroom).CompiledScript != nullptr) { compile_room_script(); if (_G(croom)->tsdatasize > 0) { if (_G(croom)->tsdatasize != (unsigned) _G(roominst)->globaldatasize) quit("room script data segment size has changed"); memcpy(&_G(roominst)->globaldata[0], _G(croom)->tsdata.data(), _G(croom)->tsdatasize); } } set_our_eip(207); _GP(play).entered_edge = -1; if ((_G(new_room_x) != SCR_NO_VALUE) && (forchar != nullptr)) { forchar->x = _G(new_room_x); forchar->y = _G(new_room_y); if (_G(new_room_placeonwalkable)) Character_PlaceOnWalkableArea(forchar); if (_G(new_room_loop) != SCR_NO_VALUE) forchar->loop = _G(new_room_loop); } // reset new_room instructions _G(new_room_x) = _G(new_room_y) = SCR_NO_VALUE; _G(new_room_loop) = SCR_NO_VALUE; _G(new_room_placeonwalkable) = false; if ((_G(new_room_pos) > 0) && (forchar != nullptr)) { if (_G(new_room_pos) >= 4000) { _GP(play).entered_edge = 3; forchar->y = _GP(thisroom).Edges.Top + get_fixed_pixel_size(1); forchar->x = _G(new_room_pos) % 1000; if (forchar->x == 0) forchar->x = _GP(thisroom).Width / 2; if (forchar->x <= _GP(thisroom).Edges.Left) forchar->x = _GP(thisroom).Edges.Left + 3; if (forchar->x >= _GP(thisroom).Edges.Right) forchar->x = _GP(thisroom).Edges.Right - 3; forchar->loop = 0; } else if (_G(new_room_pos) >= 3000) { _GP(play).entered_edge = 2; forchar->y = _GP(thisroom).Edges.Bottom - get_fixed_pixel_size(1); forchar->x = _G(new_room_pos) % 1000; if (forchar->x == 0) forchar->x = _GP(thisroom).Width / 2; if (forchar->x <= _GP(thisroom).Edges.Left) forchar->x = _GP(thisroom).Edges.Left + 3; if (forchar->x >= _GP(thisroom).Edges.Right) forchar->x = _GP(thisroom).Edges.Right - 3; forchar->loop = 3; } else if (_G(new_room_pos) >= 2000) { _GP(play).entered_edge = 1; forchar->x = _GP(thisroom).Edges.Right - get_fixed_pixel_size(1); forchar->y = _G(new_room_pos) % 1000; if (forchar->y == 0) forchar->y = _GP(thisroom).Height / 2; if (forchar->y <= _GP(thisroom).Edges.Top) forchar->y = _GP(thisroom).Edges.Top + 3; if (forchar->y >= _GP(thisroom).Edges.Bottom) forchar->y = _GP(thisroom).Edges.Bottom - 3; forchar->loop = 1; } else if (_G(new_room_pos) >= 1000) { _GP(play).entered_edge = 0; forchar->x = _GP(thisroom).Edges.Left + get_fixed_pixel_size(1); forchar->y = _G(new_room_pos) % 1000; if (forchar->y == 0) forchar->y = _GP(thisroom).Height / 2; if (forchar->y <= _GP(thisroom).Edges.Top) forchar->y = _GP(thisroom).Edges.Top + 3; if (forchar->y >= _GP(thisroom).Edges.Bottom) forchar->y = _GP(thisroom).Edges.Bottom - 3; forchar->loop = 2; } // if starts on un-walkable area if (get_walkable_area_pixel(forchar->x, forchar->y) == 0) { if (_G(new_room_pos) >= 3000) { // bottom or top of screen int tryleft = forchar->x - 1, tryright = forchar->x + 1; while (1) { if (get_walkable_area_pixel(tryleft, forchar->y) > 0) { forchar->x = tryleft; break; } if (get_walkable_area_pixel(tryright, forchar->y) > 0) { forchar->x = tryright; break; } int nowhere = 0; if (tryleft > _GP(thisroom).Edges.Left) { tryleft--; nowhere++; } if (tryright < _GP(thisroom).Edges.Right) { tryright++; nowhere++; } if (nowhere == 0) break; // no place to go, so leave him } } else if (_G(new_room_pos) >= 1000) { // left or right int tryleft = forchar->y - 1, tryright = forchar->y + 1; while (1) { if (get_walkable_area_pixel(forchar->x, tryleft) > 0) { forchar->y = tryleft; break; } if (get_walkable_area_pixel(forchar->x, tryright) > 0) { forchar->y = tryright; break; } int nowhere = 0; if (tryleft > _GP(thisroom).Edges.Top) { tryleft--; nowhere++; } if (tryright < _GP(thisroom).Edges.Bottom) { tryright++; nowhere++; } if (nowhere == 0) break; // no place to go, so leave him } } } _G(new_room_pos) = 0; } if (forchar != nullptr) { _GP(play).entered_at_x = forchar->x; _GP(play).entered_at_y = forchar->y; if (forchar->x >= _GP(thisroom).Edges.Right) _GP(play).entered_edge = 1; else if (forchar->x <= _GP(thisroom).Edges.Left) _GP(play).entered_edge = 0; else if (forchar->y >= _GP(thisroom).Edges.Bottom) _GP(play).entered_edge = 2; else if (forchar->y <= _GP(thisroom).Edges.Top) _GP(play).entered_edge = 3; } if (_GP(thisroom).Options.StartupMusic > 0) PlayMusicResetQueue(_GP(thisroom).Options.StartupMusic); set_our_eip(208); if (forchar != nullptr) { if (_GP(thisroom).Options.PlayerCharOff == 0) { forchar->on = 1; enable_cursor_mode(0); } else { forchar->on = 0; disable_cursor_mode(0); // remember which character we turned off, in case they // use SetPlyaerChracter within this room (so we re-enable // the correct character when leaving the room) _GP(play).temporarily_turned_off_character = _GP(game).playercharacter; } if (forchar->flags & CHF_FIXVIEW) ; else if (_GP(thisroom).Options.PlayerView == 0) forchar->view = forchar->defview; else forchar->view = _GP(thisroom).Options.PlayerView - 1; forchar->frame = 0; // make him standing } _G(color_map) = nullptr; set_our_eip(209); generate_light_table(); update_music_volume(); // If we are not restoring a save, update cameras to accommodate for this // new room; otherwise this is done later when cameras are recreated. if (forchar != nullptr) { if (_GP(play).IsAutoRoomViewport()) adjust_viewport_to_room(); update_all_viewcams_with_newroom(); _GP(play).UpdateRoomCameras(); // update auto tracking } init_room_drawdata(); set_our_eip(212); invalidate_screen(); for (size_t cc = 0; cc < _G(croom)->numobj; cc++) { if (_G(objs)[cc].on == 2) MergeObject(cc); } _G(new_room_flags) = 0; _GP(play).gscript_timer = -1; // avoid screw-ups with changing screens _GP(play).player_on_region = 0; // trash any input which they might have done while it was loading ags_clear_input_buffer(); // no fade in, so set the palette immediately in case of 256-col sprites if (_GP(game).color_depth > 1) setpal(); set_our_eip(220); update_polled_stuff(); debug_script_log("Now in room %d", _G(displayed_room)); GUI::MarkAllGUIForUpdate(true, true); pl_run_plugin_hooks(AGSE_ENTERROOM, _G(displayed_room)); } // new_room: changes the current room number, and loads the new room from disk void new_room(int newnum, CharacterInfo *forchar) { EndSkippingUntilCharStops(); debug_script_log("Room change requested to room %d", newnum); // we are currently running Leaves Screen scripts _G(in_leaves_screen) = newnum; // player leaves screen event run_room_event(EVROM_LEAVE); // Run the global OnRoomLeave event run_on_event(GE_LEAVE_ROOM, RuntimeScriptValue().SetInt32(_G(displayed_room))); pl_run_plugin_hooks(AGSE_LEAVEROOM, _G(displayed_room)); // update the new room number if it has been altered by OnLeave scripts newnum = _G(in_leaves_screen); _G(in_leaves_screen) = -1; if ((_G(playerchar)->following >= 0) && (_GP(game).chars[_G(playerchar)->following].room != newnum)) { // the player character is following another character, // who is not in the new room. therefore, abort the follow _G(playerchar)->following = -1; } // change rooms unload_old_room(); if (_GP(usetup).clear_cache_on_room_change) { // Delete all cached sprites _GP(spriteset).DisposeAllFreeCached(); } load_new_room(newnum, forchar); // Update background frame state (it's not a part of the RoomStatus currently) _GP(play).bg_frame = 0; _GP(play).bg_frame_locked = (_GP(thisroom).Options.Flags & kRoomFlag_BkgFrameLocked) != 0; } void set_room_placeholder() { _GP(thisroom).InitDefaults(); std::shared_ptr dummy_bg(new Bitmap(1, 1, 8)); _GP(thisroom).BgFrames[0].Graphic = dummy_bg; _GP(thisroom).HotspotMask = dummy_bg; _GP(thisroom).RegionMask = dummy_bg; _GP(thisroom).WalkAreaMask = dummy_bg; _GP(thisroom).WalkBehindMask = dummy_bg; reset_temp_room(); _G(croom) = &_GP(troom); } int find_highest_room_entered() { int qq, fndas = -1; for (qq = 0; qq < MAX_ROOMS; qq++) { if (isRoomStatusValid(qq) && (getRoomStatus(qq)->beenhere != 0)) fndas = qq; } return fndas; } void first_room_initialization() { _G(starting_room) = _G(displayed_room); _G(playerchar)->prevroom = -1; set_loop_counter(0); _G(mouse_z_was) = _G(sys_mouse_z); // Reset background frame state _GP(play).bg_frame = 0; _GP(play).bg_frame_locked = (_GP(thisroom).Options.Flags & kRoomFlag_BkgFrameLocked) != 0; } void check_new_room() { // if they're in a new room, run Player Enters Screen and on_event(ENTER_ROOM) if ((_G(in_new_room) > 0) && (_G(in_new_room) != 3)) { EventHappened evh(EV_RUNEVBLOCK, EVB_ROOM, 0, EVROM_BEFOREFADEIN, _GP(game).playercharacter); // make sure that any script calls don't re-call enters screen int newroom_was = _G(in_new_room); _G(in_new_room) = 0; _GP(play).disabled_user_interface ++; process_event(&evh); _GP(play).disabled_user_interface --; _G(in_new_room) = newroom_was; } } void compile_room_script() { cc_clear_error(); _G(roominst) = ccInstance::CreateFromScript(_GP(thisroom).CompiledScript); if (cc_has_error() || (_G(roominst) == nullptr)) { quitprintf("Unable to create local script:\n%s", cc_get_error().ErrorString.GetCStr()); } if (!_G(roominst)->ResolveScriptImports(_G(roominst)->instanceof.get())) quitprintf("Unable to resolve imports in room script:\n%s", cc_get_error().ErrorString.GetCStr()); if (!_G(roominst)->ResolveImportFixups(_G(roominst)->instanceof.get())) quitprintf("Unable to resolve import fixups in room script:\n%s", cc_get_error().ErrorString.GetCStr()); _G(roominstFork) = _G(roominst)->Fork(); if (_G(roominstFork) == nullptr) quitprintf("Unable to create forked room instance:\n%s", cc_get_error().ErrorString.GetCStr()); _GP(repExecAlways).roomHasFunction = true; _GP(lateRepExecAlways).roomHasFunction = true; _GP(getDialogOptionsDimensionsFunc).roomHasFunction = true; } void on_background_frame_change() { invalidate_screen(); mark_current_background_dirty(); // get the new frame's palette memcpy(_G(palette), _GP(thisroom).BgFrames[_GP(play).bg_frame].Palette, sizeof(RGB) * 256); // hi-colour, update the palette. It won't have an immediate effect // but will be drawn properly when the screen fades in if (_GP(game).color_depth > 1) setpal(); if (_G(in_enters_screen)) return; // Don't update the palette if it hasn't changed if (_GP(thisroom).BgFrames[_GP(play).bg_frame].IsPaletteShared) return; // 256-colours, tell it to update the palette (will actually be done as // close as possible to the screen update to prevent flicker problem) if (_GP(game).color_depth == 1) _G(bg_just_changed) = 1; } void croom_ptr_clear() { _G(croom) = nullptr; _G(objs) = nullptr; } // coordinate conversion (data) ---> game ---> (room mask) int room_to_mask_coord(int coord) { return coord * _GP(game).GetDataUpscaleMult() / _GP(thisroom).MaskResolution; } // coordinate conversion (room mask) ---> game ---> (data) int mask_to_room_coord(int coord) { return coord * _GP(thisroom).MaskResolution / _GP(game).GetDataUpscaleMult(); } void convert_move_path_to_room_resolution(MoveList *ml, int from_step, int to_step) { if (to_step < 0) to_step = ml->numstage; to_step = CLIP(to_step, 0, ml->numstage - 1); from_step = CLIP(from_step, 0, to_step); // If speed is independent from MaskResolution... if ((_GP(game).options[OPT_WALKSPEEDABSOLUTE] != 0) && _GP(game).GetDataUpscaleMult() > 1) { for (int i = from_step; i <= to_step; i++) { // ...we still need to convert from game to data coords ml->xpermove[i] = game_to_data_coord(ml->xpermove[i]); ml->ypermove[i] = game_to_data_coord(ml->ypermove[i]); } } // Skip the conversion if these are equal, as they are multiplier and divisor if (_GP(thisroom).MaskResolution == _GP(game).GetDataUpscaleMult()) return; if (from_step == 0) { ml->from = {mask_to_room_coord(ml->from.X), mask_to_room_coord(ml->from.Y)}; } for (int i = from_step; i <= to_step; i++) { ml->pos[i] = {mask_to_room_coord(ml->pos[i].X), mask_to_room_coord(ml->pos[i].Y)}; } // If speed is scaling with MaskResolution... if (_GP(game).options[OPT_WALKSPEEDABSOLUTE] == 0) { for (int i = from_step; i <= to_step; i++) { ml->xpermove[i] = mask_to_room_coord(ml->xpermove[i]); ml->ypermove[i] = mask_to_room_coord(ml->ypermove[i]); } } } //============================================================================= // // Script API Functions // //============================================================================= // ScriptDrawingSurface* (int backgroundNumber) RuntimeScriptValue Sc_Room_GetDrawingSurfaceForBackground(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJAUTO_PINT(ScriptDrawingSurface, Room_GetDrawingSurfaceForBackground); } // int (const char *property) RuntimeScriptValue Sc_Room_GetProperty(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_POBJ(Room_GetProperty, const char); } // const char* (const char *property) RuntimeScriptValue Sc_Room_GetTextProperty(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), Room_GetTextProperty, const char); } RuntimeScriptValue Sc_Room_SetProperty(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_BOOL_POBJ_PINT(Room_SetProperty, const char); } // const char* (const char *property) RuntimeScriptValue Sc_Room_SetTextProperty(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_BOOL_POBJ2(Room_SetTextProperty, const char, const char); } // int () RuntimeScriptValue Sc_Room_GetBottomEdge(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetBottomEdge); } // int () RuntimeScriptValue Sc_Room_GetColorDepth(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetColorDepth); } // int () RuntimeScriptValue Sc_Room_GetHeight(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetHeight); } // int () RuntimeScriptValue Sc_Room_GetLeftEdge(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetLeftEdge); } // const char* (int index) RuntimeScriptValue Sc_Room_GetMessages(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_PINT(const char, _GP(myScriptStringImpl), Room_GetMessages); } // int () RuntimeScriptValue Sc_Room_GetMusicOnLoad(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetMusicOnLoad); } // int () RuntimeScriptValue Sc_Room_GetObjectCount(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetObjectCount); } // int () RuntimeScriptValue Sc_Room_GetRightEdge(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetRightEdge); } // int () RuntimeScriptValue Sc_Room_GetTopEdge(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetTopEdge); } // int () RuntimeScriptValue Sc_Room_GetWidth(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT(Room_GetWidth); } // void (int xx,int yy,int mood) RuntimeScriptValue Sc_RoomProcessClick(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_VOID_PINT3(RoomProcessClick); } RuntimeScriptValue Sc_Room_Exists(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_BOOL_PINT(Room_Exists); } void RegisterRoomAPI() { ScFnRegister room_api[] = { {"Room::GetDrawingSurfaceForBackground^1", API_FN_PAIR(Room_GetDrawingSurfaceForBackground)}, {"Room::GetProperty^1", API_FN_PAIR(Room_GetProperty)}, {"Room::GetTextProperty^1", API_FN_PAIR(Room_GetTextProperty)}, {"Room::SetProperty^2", API_FN_PAIR(Room_SetProperty)}, {"Room::SetTextProperty^2", API_FN_PAIR(Room_SetTextProperty)}, {"Room::ProcessClick^3", API_FN_PAIR(RoomProcessClick)}, {"ProcessClick", API_FN_PAIR(RoomProcessClick)}, {"Room::get_BottomEdge", API_FN_PAIR(Room_GetBottomEdge)}, {"Room::get_ColorDepth", API_FN_PAIR(Room_GetColorDepth)}, {"Room::get_Height", API_FN_PAIR(Room_GetHeight)}, {"Room::get_LeftEdge", API_FN_PAIR(Room_GetLeftEdge)}, {"Room::geti_Messages", API_FN_PAIR(Room_GetMessages)}, {"Room::get_MusicOnLoad", API_FN_PAIR(Room_GetMusicOnLoad)}, {"Room::get_ObjectCount", API_FN_PAIR(Room_GetObjectCount)}, {"Room::get_RightEdge", API_FN_PAIR(Room_GetRightEdge)}, {"Room::get_TopEdge", API_FN_PAIR(Room_GetTopEdge)}, {"Room::get_Width", API_FN_PAIR(Room_GetWidth)}, {"Room::Exists", API_FN_PAIR(Room_Exists)}, }; ccAddExternalFunctions361(room_api); } } // namespace AGS3