Hubert Maier 472c51b27c
AGS: Correct spelling mistake
accomodate -> accommodate
2022-10-27 15:56:08 +02:00

1185 lines
40 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ags/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/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.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/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];
buffer[0] = 0;
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);
}
//=============================================================================
// 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;
int bkg_width = _GP(thisroom).Width;
int bkg_height = _GP(thisroom).Height;
data_to_game_coords(&bkg_width, &bkg_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);
int mask_width = bkg_width / _GP(thisroom).MaskResolution;
int mask_height = bkg_height / _GP(thisroom).MaskResolution;
_GP(thisroom).HotspotMask = FixBitmap(_GP(thisroom).HotspotMask, mask_width, mask_height);
_GP(thisroom).RegionMask = FixBitmap(_GP(thisroom).RegionMask, mask_width, mask_height);
_GP(thisroom).WalkAreaMask = FixBitmap(_GP(thisroom).WalkAreaMask, mask_width, mask_height);
for (size_t i = 0; i < _GP(thisroom).WalkAreaCount; ++i) {
_GP(thisroom).WalkAreas[i].Top = room_to_mask_coord(_GP(thisroom).WalkAreas[i].Top);
_GP(thisroom).WalkAreas[i].Bottom = room_to_mask_coord(_GP(thisroom).WalkAreas[i].Bottom);
}
}
void save_room_data_segment() {
_G(croom)->FreeScriptData();
_G(croom)->tsdatasize = _G(roominst)->globaldatasize;
if (_G(croom)->tsdatasize > 0) {
_G(croom)->tsdata = (char *)malloc(_G(croom)->tsdatasize + 10);
memcpy(_G(croom)->tsdata, &_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;
debug_script_log("Unloading room %d", _G(displayed_room));
current_fade_out_effect();
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();
delete _G(roominstFork);
delete _G(roominst);
_G(roominstFork) = nullptr;
_G(roominst) = nullptr;
} else _G(croom)->tsdatasize = 0;
memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS + 1);
_GP(play).bg_frame = 0;
_GP(play).bg_frame_locked = 0;
remove_screen_overlay(-1);
delete _G(raw_saved_screen);
_G(raw_saved_screen) = nullptr;
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 _GP(actsps) buffers to save memory, since the
// objects/characters involved probably aren't on the
// new screen. this also ensures all cached data is flushed
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<Stream> 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";
}
}
update_polled_stuff_if_runtime();
// load the room from disk
_G(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 _GP(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));
update_polled_stuff_if_runtime();
_G(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<size_t>(_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) {
update_polled_stuff_if_runtime();
_GP(thisroom).BgFrames[i].Graphic = PrepareSpriteForUse(_GP(thisroom).BgFrames[i].Graphic, false);
}
update_polled_stuff_if_runtime();
_G(our_eip) = 202;
// Update game viewports
if (_GP(game).IsLegacyLetterbox())
update_letterbox_mode();
SetMouseBounds(0, 0, 0, 0);
_G(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());
_G(our_eip) = 204;
update_polled_stuff_if_runtime();
redo_walkable_areas();
update_polled_stuff_if_runtime();
walkbehinds_recalc();
update_polled_stuff_if_runtime();
_G(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) {
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<uint16_t>(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;
}
update_polled_stuff_if_runtime();
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;
ccAddExternalDynamicObject(_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;
ccAddExternalDynamicObject(_GP(thisroom).Hotspots[cc].ScriptName, &_G(scrHotspot)[cc], &_GP(ccDynamicHotspot));
}
_G(our_eip) = 206;
update_polled_stuff_if_runtime();
_G(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);
}
_G(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);
}
update_polled_stuff_if_runtime();
_G(roominst) = nullptr;
if (_G(debug_flags) & DBG_NOSCRIPT) ;
else if (_GP(thisroom).CompiledScript != nullptr) {
compile_room_script();
if (_G(croom)->tsdatasize > 0) {
if (_G(croom)->tsdatasize != _G(roominst)->globaldatasize)
quit("room script data segment size has changed");
memcpy(&_G(roominst)->globaldata[0], _G(croom)->tsdata, _G(croom)->tsdatasize);
}
}
_G(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);
_G(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;
_G(our_eip) = 209;
update_polled_stuff_if_runtime();
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();
_G(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();
_G(our_eip) = 220;
update_polled_stuff_if_runtime();
debug_script_log("Now in room %d", _G(displayed_room));
GUI::MarkAllGUIForUpdate();
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);
update_polled_stuff_if_runtime();
// we are currently running Leaves Screen scripts
_G(in_leaves_screen) = newnum;
// player leaves screen event
run_room_event(8);
// 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;
}
update_polled_stuff_if_runtime();
// change rooms
unload_old_room();
if (_GP(usetup).clear_cache_on_room_change) {
// Delete all cached sprites
_GP(spriteset).DisposeAll();
GUI::MarkAllGUIForUpdate();
}
update_polled_stuff_if_runtime();
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<Bitmap> 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;
evh.type = EV_RUNEVBLOCK;
evh.data1 = EVB_ROOM;
evh.data2 = 0;
evh.data3 = EVROM_BEFOREFADEIN;
evh.player = _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;
}
AGS_INLINE int room_to_mask_coord(int coord) {
return coord * _GP(game).GetDataUpscaleMult() / _GP(thisroom).MaskResolution;
}
AGS_INLINE int mask_to_room_coord(int coord) {
return coord * _GP(thisroom).MaskResolution / _GP(game).GetDataUpscaleMult();
}
void convert_move_path_to_room_resolution(MoveList *ml) {
if ((_GP(game).options[OPT_WALKSPEEDABSOLUTE] != 0) && _GP(game).GetDataUpscaleMult() > 1) {
// Speeds are independent from MaskResolution
for (int i = 0; i < ml->numstage; i++) {
// ...so they are not multiplied by MaskResolution factor when converted to room coords
ml->xpermove[i] = ml->xpermove[i] / _GP(game).GetDataUpscaleMult();
ml->ypermove[i] = ml->ypermove[i] / _GP(game).GetDataUpscaleMult();
}
}
if (_GP(thisroom).MaskResolution == _GP(game).GetDataUpscaleMult())
return;
ml->fromx = mask_to_room_coord(ml->fromx);
ml->fromy = mask_to_room_coord(ml->fromy);
ml->lastx = mask_to_room_coord(ml->lastx);
ml->lasty = mask_to_room_coord(ml->lasty);
for (int i = 0; i < ml->numstage; i++) {
uint16_t lowPart = mask_to_room_coord(ml->pos[i] & 0x0000ffff);
uint16_t highPart = mask_to_room_coord((ml->pos[i] >> 16) & 0x0000ffff);
ml->pos[i] = ((int)highPart << 16) | (lowPart & 0x0000ffff);
}
if (_GP(game).options[OPT_WALKSPEEDABSOLUTE] == 0) {
// Speeds are scaling with MaskResolution
for (int i = 0; i < ml->numstage; 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_CONST_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_CONST_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() {
ccAddExternalStaticFunction("Room::GetDrawingSurfaceForBackground^1", Sc_Room_GetDrawingSurfaceForBackground);
ccAddExternalStaticFunction("Room::GetProperty^1", Sc_Room_GetProperty);
ccAddExternalStaticFunction("Room::GetTextProperty^1", Sc_Room_GetTextProperty);
ccAddExternalStaticFunction("Room::SetProperty^2", Sc_Room_SetProperty);
ccAddExternalStaticFunction("Room::SetTextProperty^2", Sc_Room_SetTextProperty);
ccAddExternalStaticFunction("Room::ProcessClick^3", Sc_RoomProcessClick);
ccAddExternalStaticFunction("ProcessClick", Sc_RoomProcessClick);
ccAddExternalStaticFunction("Room::get_BottomEdge", Sc_Room_GetBottomEdge);
ccAddExternalStaticFunction("Room::get_ColorDepth", Sc_Room_GetColorDepth);
ccAddExternalStaticFunction("Room::get_Height", Sc_Room_GetHeight);
ccAddExternalStaticFunction("Room::get_LeftEdge", Sc_Room_GetLeftEdge);
ccAddExternalStaticFunction("Room::geti_Messages", Sc_Room_GetMessages);
ccAddExternalStaticFunction("Room::get_MusicOnLoad", Sc_Room_GetMusicOnLoad);
ccAddExternalStaticFunction("Room::get_ObjectCount", Sc_Room_GetObjectCount);
ccAddExternalStaticFunction("Room::get_RightEdge", Sc_Room_GetRightEdge);
ccAddExternalStaticFunction("Room::get_TopEdge", Sc_Room_GetTopEdge);
ccAddExternalStaticFunction("Room::get_Width", Sc_Room_GetWidth);
ccAddExternalStaticFunction("Room::Exists", Sc_Room_Exists);
}
} // namespace AGS3